Sdwebimage: Sometimes WebP frames have erroneously transparent pixels

Created on 9 Sep 2019  路  14Comments  路  Source: SDWebImage/SDWebImage

This is re-opening https://github.com/SDWebImage/SDWebImage/issues/2776

I did some debugging and discovered some new information. First, here's the old issue's information. I'm using SDWebImage 5.0.6 with the SDWebImageWebPCoder 0.2.3

Issue Info

Info | Value |
-------------------------|-------------------------------------|
Platform Name | iOS
Platform Version | 12
SDWebImage Version | 5.0.6
Integration Method | cocoapods
Xcode Version | Xcode 10.2
Repro rate | 5%
Repro with our demo prj | n/a
Demo project link | n/a

Issue Description and Steps

Occasionally, some SDAnimatedImages play back with frames that contain missing pixels. These pixels, which should be filled, are instead completely transparent (alpha 100%). This is a clip of the bug observed in our app. The bug is the out-of-place white pixels that appear in one frame:
ezgif-5-ac3fa74c49fc

I can tell these pixels are transparent because they will be filled with whatever color the background view is behind the SDAnimatedImageView

Unfortunately I do not have reproduction steps. It tends to happen if >1 animated images are being downloaded/rendered at a time. I've only ever seen it happen with WebP's, but I can't be sure it only affects WebP's because it's hard to purposefully cause this bug to happen while debugging.

This seems to affect the image while it's in the memory cache. We have a tap-to-lightbox feature that uses the in-memory image from the cache by fetching it with the key (rather than copying the existing SDAnimatedImage instance). An image that has a frame with missing pixels will show this bug until it's cleared from the in-memory cache and then re-loaded from disk.

This started happening when we updated to SDWebImage 5.0.6 from 4.x

Updated information:

I found an image where this can easily occur. The image can be fetched at this URL, but webp must be in your accept header in order to get WebP rather than GIF:
https://66.media.tumblr.com/c0700230bd00b3a4f6c5fd9f52ba38d4/634b65befca84981-56/s400x600/869fe1f4b3f08c98d3504aff1f985afa5aa9a3fa.gifv

You can curl this url to get the webp:

kken@C02XG0YSJGH8 ~ $ curl --header "Accept: image/webp,image/*" https://66.media.tumblr.com/c0700230bd00b3a4f6c5fd9f52ba38d4/634b65befca84981-56/s400x600/869fe1f4b3f08c98d3504aff1f985afa5aa9a3fa.gifv > link-sunglasses.webp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  200k  100  200k    0     0   449k      0 --:--:-- --:--:-- --:--:--  449k
kken@C02XG0YSJGH8 ~ $ identify link-sunglasses.webp
link-sunglasses.webp WEBP 400x400 400x400+0+0 8-bit sRGB 205770B 0.000u 0:00.000

This issue doesn't happen 100% of the time. It's intermittent. However it's easy to get to occur if the image leaves the view and then comes back (i.e. if it's in a different tab within the app, which you leave and then return to). I'm not receiving a low memory warning when it happens, so I don't think it's due to SDWebImage flushing anything due to memory issues. However I tracked the exact problem where it happens:

https://github.com/SDWebImage/SDWebImageWebPCoder/blob/master/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m#L825

Screen Shot 2019-09-09 at 4 26 59 PM

This condition is hit only when the bug occurs. When this image loads normally, a breakpoint on this line is not tripped.

You can see that the _currentBlendIndex is 5, but the frame index is 1. So safeAnimatedImageFrameAtIndex thinks it's the end of the loop. However, it's a 6-frame animated webP, not 5, so ideally _currentBlendIndex should be 0.

Normally, this frame's _currentBlendIndex is 0. Here's a screenshot of the frame being rendered correctly:

Screen Shot 2019-09-09 at 5 04 22 PM

In this case, _currentBlendIndex == NSNotFound, so the canvas is never cleared. However, in the case where the bug occurs, _currentBlendIndex is 5, so the canvas is cleared. Frames 1 and 2 are blended in the block after // Draw from range: [startIndex, endIndex) at SDImageWebPCoder.mL838. The first pass draws frame 1. The second pass tries to draw frame 2 over frame 1 and this is the result:
Screen Shot 2019-09-09 at 5 05 59 PM

Screen Shot 2019-09-09 at 5 06 53 PM

WebP bug important

All 14 comments

Maybe 5.1.0 had fix this bug #2782 , You can hava a try!

Are you sure you use the WebPCoder with version v0.2.1 and above ?

I remember I already fix the same issue in this: https://github.com/SDWebImage/SDWebImageWebPCoder/releases/tag/0.2.1

So you can try to use this combination for Animated WebP:

  • SDWebImage ~> 5.1.0
  • SDWebImageWebPCoder ~> 0.2.1

You can see that the _currentBlendIndex is 5, but the frame index is 1. So safeAnimatedImageFrameAtIndex thinks it's the end of the loop.

The frame of WebCoder is 0-based. Only the index for libwebp C API is 1-based. (I don't know why Google design like this).
When the index of animatedImageAtIndex: is called with 5, and the self.frameCount == 6, which means the last frame, but not the next loop.
If you can provide a demo, maybe I can try to debug with it. I find it hard to reproduce it, need some special usage case (like Cell-Reusing imageView ? Multiple imageView references same WebP image ?)

I tried this combination:

SDWebImage ~> 5.1.0
SDWebImageWebPCoder ~> 0.2.1

And the bug still occured. I also tried this combo:

SDWebImage ~> 5.1.0
SDWebImageWebPCoder ~> 0.2.3

and the bug still occurred. We have a tableview full of cells that can contain SDAnimatedImageViews. We do not re-use the cells, they're always brand new.

Multiple imageView references same WebP image

This might be the issue. I'll try to build a sample project to reproduce the bug

Edit: wrong fork, sorry

@krze I can not download your demo project from the host github.tumblr.net. Could you please upload the zip archive via GitHub comment ? Or just create a remote git repo at GitHub for me to clone.

@dreampiggy My mistake, put it on the wrong github
https://github.com/krze/SDWebImage-WebP-Bug

https://github.com/krze/SDWebImage-WebP-Bug

I found the logic bug. The code comment does not match what it do.

// Draw from range: [startIndex, endIndex)
do {
    @autoreleasepool {
        [self sd_blendWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace];
    }
} while ((size_t)iter.frame_num < (endIndex + 1) && WebPDemuxNextFrame(&iter));

Before we call WebPDemuxNextFrame, the iter.frame_num is the 0-based previous frame index + 1. So the endIndex should not use +1 as well.

For example:

request index == 1
frame.fromIndex == 0
frame.endIndex == 1

it should only call sd_blendWebpImageWithCanvas once, then call sd_drawnWebpImageWithCanvas once. But however, current code cause that call sd_blendWebpImageWithCanvas twice. This is the problem.

See MR: https://github.com/SDWebImage/SDWebImageWebPCoder/pull/22

@krze SDWebImageWebPCoder 0.2.4 version should fix this issue. The issue only appears when you render the same SDAnimatedimage instance by different imageViews at the same time.

I did some testing and I see that this is fixed for some cases. However, I can still get the glitch to occur in our production app when pulling to refresh a tableview, and the animated webp is in the first cell.

I'll see if it can be reproduced in the sample app, but this is some progress, thank you 馃憤

@krze Any update ? Is this issue still exist ? A new producible demo is better.

I haven't had the time to update the demo yet. I'll try to get it to you this week. Sorry

@dreampiggy After further testing I can no longer reproduce the issue. I'm not sure why I originally saw it again under certain conditions in 0.2.4, but I can no longer reproduce the issue with the same steps.

If I find a way to repro the issue again I'll open a new thread. Thank you for the fix!

Was this page helpful?
0 / 5 - 0 ratings