Changeset 207386 in webkit


Ignore:
Timestamp:
Oct 15, 2016 5:19:22 PM (8 years ago)
Author:
commit-queue@webkit.org
Message:

Delete the animated image catchup code
https://bugs.webkit.org/show_bug.cgi?id=163410

Patch by Said Abou-Hallawa <sabouhallawa@apple.com> on 2016-10-15
Reviewed by Simon Fraser.

Source/WebCore:

This patch fixes two issues in the animated image workflow:

1) Setting the animation timer should follow the following rules:

-- Initial case: Which happens before drawing the first frame. We
should set the timer to fire after the current_frame_duration.

-- Late case (Slow animation): This happens if the current_time is
past the next_frame_desired_time. In this case we should fire the
timer immediately.

-- Early case (Fast animation): This happens when there is still time
before the next_frame_desired_time. In this case we should set the
timer to fire after the difference between the next_frame_desired_time
and the current_time.

2) Deleting the code for catching up the current_frame:

This code used to run in the slow animation case. It was never used
on iOS. It was trying to adjust the current_frame according to the
current_time as if there were no delay. It turned out that this might
cause a bigger delay because most likely the decoder decodes the image
frames incrementally; i.e. to decode frame k, it has to have frame
(k - 1) decoded.

Test: fast/images/ordered-animated-image-frames.html

  • platform/graphics/BitmapImage.cpp:

(WebCore::BitmapImage::draw): Remove the iOS specific code.
(WebCore::BitmapImage::startAnimation): Move the animation finishing code from
BitmapImage::internalAdvanceAnimation() to this function. Simplify the timer
duration code as it is described above.

(WebCore::BitmapImage::advanceAnimation): Merge BitmapImage::internalAdvanceAnimation()
into this function.

(WebCore::BitmapImage::resetAnimation):

(WebCore::BitmapImage::internalAdvanceAnimation): Deleted.

  • platform/graphics/BitmapImage.h:
  • platform/graphics/Image.cpp:

(WebCore::Image::drawTiled):

  • platform/graphics/Image.h:

(WebCore::Image::startAnimation):

  • svg/graphics/SVGImage.cpp:

(WebCore::SVGImage::startAnimation):

  • svg/graphics/SVGImage.h:

Remove the catchup code form the Image and SVGImage classes.

LayoutTests:

This animated gif has one red frame, one green frame and two red frames.
The test page renders only two frames from this this image on a canvas. The
test passes if the second frame (the green one) is rendered on the canvas
even if drawImage() is called after the duration of the first frame.

  • fast/images/ordered-animated-image-frames-expected.html: Added.
  • fast/images/ordered-animated-image-frames.html: Added.
  • fast/images/resources/animated-red-green-blue.gif: Added.
Location:
trunk
Files:
3 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r207383 r207386  
     12016-10-15  Said Abou-Hallawa  <sabouhallawa@apple.com>
     2
     3        Delete the animated image catchup code
     4        https://bugs.webkit.org/show_bug.cgi?id=163410
     5
     6        Reviewed by Simon Fraser.
     7
     8        This animated gif has one red frame, one green frame and two red frames.
     9        The test page renders only two frames from this this image on a canvas. The
     10        test passes if the second frame (the green one) is rendered on the canvas
     11        even if drawImage() is called after the duration of the first frame.
     12
     13        * fast/images/ordered-animated-image-frames-expected.html: Added.
     14        * fast/images/ordered-animated-image-frames.html: Added.
     15        * fast/images/resources/animated-red-green-blue.gif: Added.
     16
    1172016-10-15  Myles C. Maxfield  <mmaxfield@apple.com>
    218
  • trunk/Source/WebCore/ChangeLog

    r207384 r207386  
     12016-10-15  Said Abou-Hallawa  <sabouhallawa@apple.com>
     2
     3        Delete the animated image catchup code
     4        https://bugs.webkit.org/show_bug.cgi?id=163410
     5
     6        Reviewed by Simon Fraser.
     7
     8        This patch fixes two issues in the animated image workflow:
     9
     10        1) Setting the animation timer should follow the following rules:
     11       
     12            -- Initial case: Which happens before drawing the first frame. We
     13            should set the timer to fire after the current_frame_duration.
     14
     15            -- Late case (Slow animation): This happens if the current_time is
     16            past the next_frame_desired_time. In this case we should fire the
     17            timer immediately.
     18
     19            -- Early case (Fast animation): This happens when there is still time
     20            before the next_frame_desired_time. In this case we should set the
     21            timer to fire after the difference between the next_frame_desired_time
     22            and the current_time.
     23
     24        2) Deleting the code for catching up the current_frame:
     25       
     26            This code used to run in the slow animation case. It was never used
     27            on iOS. It was trying to adjust the current_frame according to the
     28            current_time as if there were no delay. It turned out that this might
     29            cause a bigger delay because most likely the decoder decodes the image
     30            frames incrementally; i.e. to decode frame k, it has to have frame
     31            (k - 1) decoded.
     32
     33        Test: fast/images/ordered-animated-image-frames.html
     34
     35        * platform/graphics/BitmapImage.cpp:
     36        (WebCore::BitmapImage::draw): Remove the iOS specific code.
     37        (WebCore::BitmapImage::startAnimation): Move the animation finishing code from
     38        BitmapImage::internalAdvanceAnimation() to this function. Simplify the timer
     39        duration code as it is described above.
     40
     41        (WebCore::BitmapImage::advanceAnimation): Merge BitmapImage::internalAdvanceAnimation()
     42        into this function.
     43
     44        (WebCore::BitmapImage::resetAnimation):
     45       
     46        (WebCore::BitmapImage::internalAdvanceAnimation): Deleted.
     47        * platform/graphics/BitmapImage.h:
     48
     49        * platform/graphics/Image.cpp:
     50        (WebCore::Image::drawTiled):
     51        * platform/graphics/Image.h:
     52        (WebCore::Image::startAnimation):
     53        * svg/graphics/SVGImage.cpp:
     54        (WebCore::SVGImage::startAnimation):
     55        * svg/graphics/SVGImage.h:
     56        Remove the catchup code form the Image and SVGImage classes.
     57
    1582016-10-15  Darin Adler  <darin@apple.com>
    259
  • trunk/Source/WebCore/platform/graphics/BitmapImage.cpp

    r207357 r207386  
    142142        return;
    143143
    144 #if PLATFORM(IOS)
    145     startAnimation(DoNotCatchUp);
    146 #else
    147144    startAnimation();
    148 #endif
    149145
    150146    Color color = singlePixelSolidColor();
     
    228224}
    229225
    230 void BitmapImage::startAnimation(CatchUpAnimation catchUpIfNecessary)
     226void BitmapImage::startAnimation()
    231227{
    232228    if (m_frameTimer || !shouldAnimate() || frameCount() <= 1)
    233229        return;
    234230
    235     // If we aren't already animating, set now as the animation start time.
    236     const double time = monotonicallyIncreasingTime();
    237     if (!m_desiredFrameStartTime)
    238         m_desiredFrameStartTime = time;
     231    if (m_currentFrame >= frameCount() - 1) {
     232        // Don't advance past the last frame if we haven't decoded the whole image
     233        // yet and our repetition count is potentially unset. The repetition count
     234        // in a GIF can potentially come after all the rest of the image data, so
     235        // wait on it.
     236        if (!m_source.isAllDataReceived() && repetitionCount() == RepetitionCountOnce)
     237            return;
     238
     239        ++m_repetitionsComplete;
     240
     241        // Check for the end of animation.
     242        if (repetitionCount() != RepetitionCountInfinite && m_repetitionsComplete > repetitionCount()) {
     243            m_animationFinished = true;
     244            destroyDecodedDataIfNecessary(false);
     245            return;
     246        }
     247
     248        destroyDecodedDataIfNecessary(true);
     249    }
    239250
    240251    // Don't advance the animation to an incomplete frame.
     
    243254        return;
    244255
    245     // Don't advance past the last frame if we haven't decoded the whole image
    246     // yet and our repetition count is potentially unset. The repetition count
    247     // in a GIF can potentially come after all the rest of the image data, so
    248     // wait on it.
    249     if (!m_source.isAllDataReceived() && repetitionCount() == RepetitionCountOnce && m_currentFrame >= (frameCount() - 1))
    250         return;
    251 
    252     // Determine time for next frame to start. By ignoring paint and timer lag
    253     // in this calculation, we make the animation appear to run at its desired
    254     // rate regardless of how fast it's being repainted.
    255     const double currentDuration = frameDurationAtIndex(m_currentFrame);
    256     m_desiredFrameStartTime += currentDuration;
    257 
    258 #if !PLATFORM(IOS)
    259     // When an animated image is more than five minutes out of date, the
    260     // user probably doesn't care about resyncing and we could burn a lot of
    261     // time looping through frames below. Just reset the timings.
    262     const double cAnimationResyncCutoff = 5 * 60;
    263     if ((time - m_desiredFrameStartTime) > cAnimationResyncCutoff)
    264         m_desiredFrameStartTime = time + currentDuration;
    265 #else
    266     // Maintaining frame-to-frame delays is more important than
    267     // maintaining absolute animation timing, so reset the timings each frame.
    268     m_desiredFrameStartTime = time + currentDuration;
    269 #endif
    270 
    271     // The image may load more slowly than it's supposed to animate, so that by
    272     // the time we reach the end of the first repetition, we're well behind.
    273     // Clamp the desired frame start time in this case, so that we don't skip
    274     // frames (or whole iterations) trying to "catch up". This is a tradeoff:
    275     // It guarantees users see the whole animation the second time through and
    276     // don't miss any repetitions, and is closer to what other browsers do; on
    277     // the other hand, it makes animations "less accurate" for pages that try to
    278     // sync an image and some other resource (e.g. audio), especially if users
    279     // switch tabs (and thus stop drawing the animation, which will pause it)
    280     // during that initial loop, then switch back later.
    281     if (nextFrame == 0 && m_repetitionsComplete == 0 && m_desiredFrameStartTime < time)
     256    double time = monotonicallyIncreasingTime();
     257
     258    // Handle initial state.
     259    if (!m_desiredFrameStartTime)
    282260        m_desiredFrameStartTime = time;
    283261
    284     if (catchUpIfNecessary == DoNotCatchUp || time < m_desiredFrameStartTime) {
    285         // Haven't yet reached time for next frame to start; delay until then.
    286         startTimer(std::max<double>(m_desiredFrameStartTime - time, 0));
    287         return;
    288     }
     262    // Setting 'm_desiredFrameStartTime' to 'time' means we are late; otherwise we are early.
     263    m_desiredFrameStartTime = std::max(time, m_desiredFrameStartTime + frameDurationAtIndex(m_currentFrame));
    289264
    290265    ASSERT(!m_frameTimer);
    291 
    292     // We've already reached or passed the time for the next frame to start.
    293     // See if we've also passed the time for frames after that to start, in
    294     // case we need to skip some frames entirely. Remember not to advance
    295     // to an incomplete frame.
    296 
    297 #if !LOG_DISABLED
    298     size_t startCatchupFrameIndex = nextFrame;
    299 #endif
    300 
    301     for (size_t frameAfterNext = (nextFrame + 1) % frameCount(); frameIsCompleteAtIndex(frameAfterNext); frameAfterNext = (nextFrame + 1) % frameCount()) {
    302         // Should we skip the next frame?
    303         double frameAfterNextStartTime = m_desiredFrameStartTime + frameDurationAtIndex(nextFrame);
    304         if (time < frameAfterNextStartTime)
    305             break;
    306 
    307         // Yes; skip over it without notifying our observers. If we hit the end while catching up,
    308         // tell the observer asynchronously.
    309         if (!internalAdvanceAnimation(SkippingFramesToCatchUp)) {
    310             m_animationFinishedWhenCatchingUp = true;
    311             startTimer(0);
    312             LOG(Images, "BitmapImage %p startAnimation catching up from frame %lu, ended", this, startCatchupFrameIndex);
    313             return;
    314         }
    315         m_desiredFrameStartTime = frameAfterNextStartTime;
    316         nextFrame = frameAfterNext;
    317     }
    318 
    319     LOG(Images, "BitmapImage %p startAnimation catching up jumped from from frame %lu to %d", this, startCatchupFrameIndex, (int)nextFrame - 1);
    320 
    321     // Draw the next frame as soon as possible. Note that m_desiredFrameStartTime
    322     // may be in the past, meaning the next time through this function we'll
    323     // kick off the next advancement sooner than this frame's duration would suggest.
    324     startTimer(0);
     266    startTimer(m_desiredFrameStartTime - time);
    325267}
    326268
    327269void BitmapImage::advanceAnimation()
    328270{
    329     internalAdvanceAnimation();
    330     // At this point the image region has been marked dirty, and if it's
    331     // onscreen, we'll soon make a call to draw(), which will call
    332     // startAnimation() again to keep the animation moving.
    333 }
    334 
    335 bool BitmapImage::internalAdvanceAnimation(AnimationAdvancement advancement)
    336 {
    337271    clearTimer();
    338272
    339     if (m_animationFinishedWhenCatchingUp) {
     273    m_currentFrame = (m_currentFrame + 1) % frameCount();
     274    destroyDecodedDataIfNecessary(false);
     275
     276    if (imageObserver())
    340277        imageObserver()->animationAdvanced(this);
    341         m_animationFinishedWhenCatchingUp = false;
    342         return false;
    343     }
    344 
    345     ++m_currentFrame;
    346     bool advancedAnimation = true;
    347     bool destroyAll = false;
    348     if (m_currentFrame >= frameCount()) {
    349         ++m_repetitionsComplete;
    350 
    351         // Get the repetition count again. If we weren't able to get a
    352         // repetition count before, we should have decoded the whole image by
    353         // now, so it should now be available.
    354         if (repetitionCount() != RepetitionCountInfinite && m_repetitionsComplete > repetitionCount()) {
    355             m_animationFinished = true;
    356             m_desiredFrameStartTime = 0;
    357             --m_currentFrame;
    358             advancedAnimation = false;
    359         } else {
    360             m_currentFrame = 0;
    361             destroyAll = true;
    362         }
    363     }
    364     destroyDecodedDataIfNecessary(destroyAll);
    365 
    366     // We need to draw this frame if we advanced to it while not skipping, or if
    367     // while trying to skip frames we hit the last frame and thus had to stop.
    368     if (advancement == Normal && advancedAnimation)
    369         imageObserver()->animationAdvanced(this);
    370 
    371     return advancedAnimation;
    372278}
    373279
     
    383289    stopAnimation();
    384290    m_currentFrame = 0;
    385     m_repetitionsComplete = 0;
     291    m_repetitionsComplete = RepetitionCountNone;
    386292    m_desiredFrameStartTime = 0;
    387293    m_animationFinished = false;
  • trunk/Source/WebCore/platform/graphics/BitmapImage.h

    r207357 r207386  
    153153    bool shouldAnimate();
    154154    bool canAnimate();
    155     void startAnimation(CatchUpAnimation = CatchUp) override;
     155    void startAnimation() override;
    156156    void advanceAnimation();
    157 
    158     // Function that does the real work of advancing the animation. When
    159     // skippingFrames is true, we're in the middle of a loop trying to skip over
    160     // a bunch of animation frames, so we should not do things like decode each
    161     // one or notify our observers.
    162     // Returns whether the animation was advanced.
    163     enum AnimationAdvancement { Normal, SkippingFramesToCatchUp };
    164     bool internalAdvanceAnimation(AnimationAdvancement = Normal);
    165157
    166158    // It may look unusual that there is no start animation call as public API. This is because
     
    194186    double m_desiredFrameStartTime { 0 }; // The system time at which we hope to see the next call to startAnimation().
    195187    bool m_animationFinished { false };
    196     bool m_animationFinishedWhenCatchingUp { false };
    197188
    198189#if USE(APPKIT)
  • trunk/Source/WebCore/platform/graphics/Image.cpp

    r207028 r207386  
    195195    FloatRect tileRect(FloatPoint(), intrinsicTileSize);
    196196    drawPattern(ctxt, destRect, tileRect, patternTransform, oneTileRect.location(), spacing, op, blendMode);
    197 
    198 #if PLATFORM(IOS)
    199     startAnimation(DoNotCatchUp);
    200 #else
    201197    startAnimation();
    202 #endif
    203198}
    204199
     
    280275    FloatPoint patternPhase(dstRect.x() - hPhase, dstRect.y() - vPhase);
    281276    drawPattern(ctxt, dstRect, srcRect, patternTransform, patternPhase, spacing, op);
    282 
    283 #if PLATFORM(IOS)
    284     startAnimation(DoNotCatchUp);
    285 #else
    286277    startAnimation();
    287 #endif
    288278}
    289279
  • trunk/Source/WebCore/platform/graphics/Image.h

    r207357 r207386  
    127127    // Animation begins whenever someone draws the image, so startAnimation() is not normally called.
    128128    // It will automatically pause once all observers no longer want to render the image anywhere.
    129     enum CatchUpAnimation { DoNotCatchUp, CatchUp };
    130     virtual void startAnimation(CatchUpAnimation = CatchUp) { }
     129    virtual void startAnimation() { }
    131130    virtual void stopAnimation() {}
    132131    virtual void resetAnimation() {}
  • trunk/Source/WebCore/svg/graphics/SVGImage.cpp

    r207357 r207386  
    373373}
    374374
    375 // FIXME: support catchUpIfNecessary.
    376 void SVGImage::startAnimation(CatchUpAnimation)
     375void SVGImage::startAnimation()
    377376{
    378377    SVGSVGElement* rootElement = this->rootElement();
  • trunk/Source/WebCore/svg/graphics/SVGImage.h

    r207357 r207386  
    6262    bool hasRelativeHeight() const final;
    6363
    64     void startAnimation(CatchUpAnimation = CatchUp) final;
     64    void startAnimation() final;
    6565    void stopAnimation() final;
    6666    void resetAnimation() final;
Note: See TracChangeset for help on using the changeset viewer.