Changeset 93580 in webkit


Ignore:
Timestamp:
Aug 22, 2011 10:45:49 PM (13 years ago)
Author:
commit-queue@webkit.org
Message:

Implemented skia support for caching resizes of cropped images.
https://bugs.webkit.org/show_bug.cgi?id=65587

Patch by John Bates <jbates@google.com> on 2011-08-22
Reviewed by Darin Fisher.

Previously, resizes of cropped images would not be cached. This causes various websites to have janky CSS animations in software compositing mode.

  • platform/graphics/skia/ImageSkia.cpp:

(WebCore::drawResampledBitmap): Changed to use new APIs for subset caching.
(WebCore::Image::drawPattern): Added allowCaching parameter.

  • platform/graphics/skia/NativeImageSkia.cpp:

(WebCore::NativeImageSkia::NativeImageSkia):
(WebCore::NativeImageSkia::CachedImageInfo::CachedImageInfo):
(WebCore::NativeImageSkia::CachedImageInfo::isEqual):
(WebCore::NativeImageSkia::CachedImageInfo::set):
(WebCore::NativeImageSkia::hasResizedBitmap): Changed this method so that it does not modify caching data. Added a second version used for cropped image resizes.
(WebCore::NativeImageSkia::resizedBitmap): Added parameter to let caller specify whether caching is allowed.
(WebCore::NativeImageSkia::shouldCacheResampling): Added a second version used for cropped image resizes.
(WebCore::NativeImageSkia::shouldCacheResamplingInternal): Both shouldCacheResampling methods call down to this for the shared logic.

  • platform/graphics/skia/NativeImageSkia.h: Added CachedImageInfo to uniquely identify the cached or requested image resize operation.
Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/platform/chromium/test_expectations.txt

    r93570 r93580  
    36213621BUGWK65462 VISTA : http/tests/cache/history-only-cached-subresource-loads-max-age-https.html = PASS TIMEOUT
    36223622
     3623// Needs new baselines for change to image resize caching code.
     3624BUGWK65587 : svg/dynamic-updates/SVGFEImageElement-dom-preserveAspectRatio-attr.html = IMAGE
     3625BUGWK65587 : svg/dynamic-updates/SVGFEImageElement-svgdom-preserveAspectRatio-prop.html = IMAGE
     3626BUGWK65587 : svg/custom/image-small-width-height.svg = IMAGE
     3627
    36233628// Introduced in r92298, which might cause another test crashing.
    36243629BUGZMO SKIP : fast/loader/reload-zero-byte-plugin.html = FAIL
  • trunk/Source/WebCore/ChangeLog

    r93579 r93580  
     12011-08-22  John Bates  <jbates@google.com>
     2
     3        Implemented skia support for caching resizes of cropped images.
     4        https://bugs.webkit.org/show_bug.cgi?id=65587
     5
     6        Reviewed by Darin Fisher.
     7
     8        Previously, resizes of cropped images would not be cached. This causes various websites to have janky CSS animations in software compositing mode.
     9
     10        * platform/graphics/skia/ImageSkia.cpp:
     11        (WebCore::drawResampledBitmap): Changed to use new APIs for subset caching.
     12        (WebCore::Image::drawPattern): Added allowCaching parameter.
     13        * platform/graphics/skia/NativeImageSkia.cpp:
     14        (WebCore::NativeImageSkia::NativeImageSkia):
     15        (WebCore::NativeImageSkia::CachedImageInfo::CachedImageInfo):
     16        (WebCore::NativeImageSkia::CachedImageInfo::isEqual):
     17        (WebCore::NativeImageSkia::CachedImageInfo::set):
     18        (WebCore::NativeImageSkia::hasResizedBitmap): Changed this method so that it does not modify caching data. Added a second version used for cropped image resizes.
     19        (WebCore::NativeImageSkia::resizedBitmap): Added parameter to let caller specify whether caching is allowed.
     20        (WebCore::NativeImageSkia::shouldCacheResampling): Added a second version used for cropped image resizes.
     21        (WebCore::NativeImageSkia::shouldCacheResamplingInternal): Both shouldCacheResampling methods call down to this for the shared logic.
     22        * platform/graphics/skia/NativeImageSkia.h: Added CachedImageInfo to uniquely identify the cached or requested image resize operation.
     23
    1242011-08-22  Tony Gentilcore  <tonyg@chromium.org>
    225
  • trunk/Source/WebCore/platform/graphics/skia/ImageSkia.cpp

    r93013 r93580  
    162162// Resampling the whole image every time is very slow, so this speeds up things
    163163// dramatically.
     164//
     165// Note: this code is only used when the canvas transformation is limited to
     166// scaling or translation.
    164167static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkIRect& srcIRect, const SkRect& destRect)
    165168{
    166     // First get the subset we need. This is efficient and does not copy pixels.
    167     SkBitmap subset;
    168     bitmap.extractSubset(&subset, srcIRect);
    169     SkRect srcRect;
    170     srcRect.set(srcIRect);
    171 
    172     // Whether we're doing a subset or using the full source image.
    173     bool srcIsFull = srcIRect.fLeft == 0 && srcIRect.fTop == 0
    174         && srcIRect.width() == bitmap.width()
    175         && srcIRect.height() == bitmap.height();
    176 
    177     // We will always draw in integer sizes, so round the destination rect.
    178     SkIRect destRectRounded;
    179     destRect.round(&destRectRounded);
    180     SkIRect resizedImageRect =  // Represents the size of the resized image.
    181         { 0, 0, destRectRounded.width(), destRectRounded.height() };
    182 
    183169    // Apply forward transform to destRect to estimate required size of
    184170    // re-sampled bitmap, and use only in calls required to resize, or that
     
    189175    destRectTransformed.round(&destRectTransformedRounded);
    190176
    191     if (srcIsFull && bitmap.hasResizedBitmap(destRectTransformedRounded.width(), destRectTransformedRounded.height())) {
    192         // Yay, this bitmap frame already has a resized version.
    193         SkBitmap resampled = bitmap.resizedBitmap(destRectTransformedRounded.width(), destRectTransformedRounded.height());
    194         canvas.drawBitmapRect(resampled, 0, destRect, &paint);
    195         return;
    196     }
    197 
    198177    // Compute the visible portion of our rect.
    199     // We also need to compute the transformed portion of the
    200     // visible portion for use below.
    201     SkRect destBitmapSubsetSk;
    202     ClipRectToCanvas(canvas, destRect, &destBitmapSubsetSk);
    203     SkRect destBitmapSubsetTransformed;
    204     canvas.getTotalMatrix().mapRect(&destBitmapSubsetTransformed, destBitmapSubsetSk);
    205     destBitmapSubsetSk.offset(-destRect.fLeft, -destRect.fTop);
     178    SkRect destRectVisibleSubset;
     179    ClipRectToCanvas(canvas, destRect, &destRectVisibleSubset);
     180    // ClipRectToCanvas often overshoots, resulting in a larger region than our
     181    // original destRect. Intersecting gets us back inside.
     182    if (!destRectVisibleSubset.intersect(destRect))
     183        return; // Nothing visible in destRect.
     184
     185    // Compute the transformed (screen space) portion of the visible portion for
     186    // use below.
     187    SkRect destRectVisibleSubsetTransformed;
     188    canvas.getTotalMatrix().mapRect(&destRectVisibleSubsetTransformed, destRectVisibleSubset);
     189    SkRect destBitmapSubsetTransformed = destRectVisibleSubsetTransformed;
     190    destBitmapSubsetTransformed.offset(-destRectTransformed.fLeft,
     191                                       -destRectTransformed.fTop);
    206192    SkIRect destBitmapSubsetTransformedRounded;
    207193    destBitmapSubsetTransformed.round(&destBitmapSubsetTransformedRounded);
    208     destBitmapSubsetTransformedRounded.offset(-destRectTransformedRounded.fLeft, -destRectTransformedRounded.fTop);
    209 
    210     // The matrix inverting, etc. could have introduced rounding error which
    211     // causes the bounds to be outside of the resized bitmap. We round outward
    212     // so we always lean toward it being larger rather than smaller than we
    213     // need, and then clamp to the bitmap bounds so we don't get any invalid
    214     // data.
    215     SkIRect destBitmapSubsetSkI;
    216     destBitmapSubsetSk.roundOut(&destBitmapSubsetSkI);
    217     if (!destBitmapSubsetSkI.intersect(resizedImageRect))
    218         return;  // Resized image does not intersect.
    219 
    220     if (srcIsFull && bitmap.shouldCacheResampling(
    221             resizedImageRect.width(),
    222             resizedImageRect.height(),
    223             destBitmapSubsetSkI.width(),
    224             destBitmapSubsetSkI.height())) {
    225         // We're supposed to resize the entire image and cache it, even though
    226         // we don't need all of it.
    227         SkBitmap resampled = bitmap.resizedBitmap(destRectTransformedRounded.width(),
    228                                                   destRectTransformedRounded.height());
    229         canvas.drawBitmapRect(resampled, 0, destRect, &paint);
    230     } else {
    231         // We should only resize the exposed part of the bitmap to do the
    232         // minimal possible work.
    233 
    234         // Resample the needed part of the image.
    235         // Transforms above plus rounding may cause destBitmapSubsetTransformedRounded
    236         // to go outside the image, so need to clip to avoid problems.
    237         if (destBitmapSubsetTransformedRounded.intersect(0, 0,
    238                 destRectTransformedRounded.width(), destRectTransformedRounded.height())) {
    239 
    240             SkBitmap resampled = skia::ImageOperations::Resize(subset,
    241                 skia::ImageOperations::RESIZE_LANCZOS3,
    242                 destRectTransformedRounded.width(), destRectTransformedRounded.height(),
    243                 destBitmapSubsetTransformedRounded);
    244 
    245             // Compute where the new bitmap should be drawn. Since our new bitmap
    246             // may be smaller than the original, we have to shift it over by the
    247             // same amount that we cut off the top and left.
    248             destBitmapSubsetSkI.offset(destRect.fLeft, destRect.fTop);
    249             SkRect offsetDestRect;
    250             offsetDestRect.set(destBitmapSubsetSkI);
    251 
    252             canvas.drawBitmapRect(resampled, 0, offsetDestRect, &paint);
    253         }
    254     }
     194
     195    // Transforms above plus rounding may cause destBitmapSubsetTransformedRounded
     196    // to go outside the image, so need to clip to avoid problems.
     197    if (!destBitmapSubsetTransformedRounded.intersect(
     198            0, 0, destRectTransformedRounded.width(), destRectTransformedRounded.height()))
     199        return; // Image is not visible.
     200
     201    SkBitmap resampled = bitmap.resizedBitmap(srcIRect,
     202                                              destRectTransformedRounded.width(),
     203                                              destRectTransformedRounded.height(),
     204                                              destBitmapSubsetTransformedRounded);
     205    canvas.drawBitmapRect(resampled, 0, destRectVisibleSubset, &paint);
    255206}
    256207
     
    341292    FloatRect normSrcRect = normalizeRect(floatSrcRect);
    342293    if (destRect.isEmpty() || normSrcRect.isEmpty())
    343         return;  // nothing to draw
     294        return; // nothing to draw
    344295
    345296    NativeImageSkia* bitmap = nativeImageForCurrentFrame();
     
    347298        return;
    348299
    349     // This is a very inexpensive operation. It will generate a new bitmap but
    350     // it will internally reference the old bitmap's pixels, adjusting the row
    351     // stride so the extra pixels appear as padding to the subsetted bitmap.
    352     SkBitmap srcSubset;
    353300    SkIRect srcRect = enclosingIntRect(normSrcRect);
    354     bitmap->extractSubset(&srcSubset, srcRect);
    355 
    356     SkBitmap resampled;
    357     SkShader* shader;
    358301
    359302    // Figure out what size the bitmap will be in the destination. The
    360303    // destination rect is the bounds of the pattern, we need to use the
    361     // matrix to see how bit it will be.
     304    // matrix to see how big it will be.
    362305    float destBitmapWidth, destBitmapHeight;
    363306    TransformDimensions(patternTransform, srcRect.width(), srcRect.height(),
     
    366309    // Compute the resampling mode.
    367310    ResamplingMode resampling;
    368     if (context->platformContext()->isAccelerated())
     311    if (context->platformContext()->isAccelerated() || context->platformContext()->printing())
    369312        resampling = RESAMPLE_LINEAR;
    370     else {
    371         if (context->platformContext()->printing())
    372             resampling = RESAMPLE_LINEAR;
    373         else
    374             resampling = computeResamplingMode(context->platformContext(), *bitmap, srcRect.width(), srcRect.height(), destBitmapWidth, destBitmapHeight);
    375     }
     313    else
     314        resampling = computeResamplingMode(context->platformContext(), *bitmap, srcRect.width(), srcRect.height(), destBitmapWidth, destBitmapHeight);
    376315
    377316    // Load the transform WebKit requested.
    378317    SkMatrix matrix(patternTransform);
    379318
     319    SkShader* shader;
    380320    if (resampling == RESAMPLE_AWESOME) {
    381321        // Do nice resampling.
    382         SkBitmap resampled;
    383322        int width = static_cast<int>(destBitmapWidth);
    384323        int height = static_cast<int>(destBitmapHeight);
    385         if (!srcRect.fLeft && !srcRect.fTop
    386             && srcRect.fRight == bitmap->width() && srcRect.fBottom == bitmap->height()
    387             && (bitmap->hasResizedBitmap(width, height)
    388                 || bitmap->shouldCacheResampling(width, height, width, height))) {
    389             // resizedBitmap() caches resized image.
    390             resampled = bitmap->resizedBitmap(width, height);
    391         } else {
    392             resampled = skia::ImageOperations::Resize(srcSubset,
    393                 skia::ImageOperations::RESIZE_LANCZOS3, width, height);
    394         }
     324        SkBitmap resampled = bitmap->resizedBitmap(srcRect, width, height);
    395325        shader = SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
    396326
     
    401331    } else {
    402332        // No need to do nice resampling.
     333        SkBitmap srcSubset;
     334        bitmap->extractSubset(&srcSubset, srcRect);
    403335        shader = SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode);
    404336    }
  • trunk/Source/WebCore/platform/graphics/skia/NativeImageSkia.cpp

    r90872 r93580  
    4141NativeImageSkia::NativeImageSkia()
    4242    : m_isDataComplete(false),
    43       m_lastRequestSize(0, 0),
    4443      m_resizeRequests(0)
    4544{
     
    4948    : SkBitmap(other),
    5049      m_isDataComplete(false),
    51       m_lastRequestSize(0, 0),
    5250      m_resizeRequests(0)
    5351{
    5452}
    55 
    5653
    5754NativeImageSkia::~NativeImageSkia()
     
    6461}
    6562
    66 bool NativeImageSkia::hasResizedBitmap(int w, int h) const
     63bool NativeImageSkia::hasResizedBitmap(const SkIRect& srcSubset, int destWidth, int destHeight) const
    6764{
    68     if (m_lastRequestSize.width() == w && m_lastRequestSize.height() == h)
    69         m_resizeRequests++;
    70     else {
    71         m_lastRequestSize = IntSize(w, h);
    72         m_resizeRequests = 0;
     65    return m_cachedImageInfo.isEqual(srcSubset, destWidth, destHeight) && !m_resizedImage.empty();
     66}
     67
     68SkBitmap NativeImageSkia::resizedBitmap(const SkIRect& srcSubset,
     69                                        int destWidth,
     70                                        int destHeight,
     71                                        const SkIRect& destVisibleSubset) const
     72{
     73    if (!hasResizedBitmap(srcSubset, destWidth, destHeight)) {
     74        bool shouldCache = m_isDataComplete
     75            && shouldCacheResampling(srcSubset, destWidth, destHeight, destVisibleSubset);
     76
     77        SkBitmap subset;
     78        extractSubset(&subset, srcSubset);
     79        if (!shouldCache) {
     80            // Just resize the visible subset and return it.
     81            SkBitmap resizedImage = skia::ImageOperations::Resize(subset, skia::ImageOperations::RESIZE_LANCZOS3, destWidth, destHeight, destVisibleSubset);
     82            return resizedImage;
     83        }
     84
     85        m_resizedImage = skia::ImageOperations::Resize(subset, skia::ImageOperations::RESIZE_LANCZOS3, destWidth, destHeight);
    7386    }
    7487
    75     return m_resizedImage.width() == w && m_resizedImage.height() == h;
     88    SkBitmap visibleBitmap;
     89    m_resizedImage.extractSubset(&visibleBitmap, destVisibleSubset);
     90    return visibleBitmap;
    7691}
    7792
    78 // FIXME: don't cache when image is in-progress.
     93bool NativeImageSkia::shouldCacheResampling(const SkIRect& srcSubset,
     94                                            int destWidth,
     95                                            int destHeight,
     96                                            const SkIRect& destVisibleSubset) const
     97{
     98    // Check whether the requested dimensions match previous request.
     99    bool matchesPreviousRequest = m_cachedImageInfo.isEqual(srcSubset, destWidth, destHeight);
     100    if (matchesPreviousRequest)
     101        ++m_resizeRequests;
     102    else {
     103        m_cachedImageInfo.set(srcSubset, destWidth, destHeight);
     104        m_resizeRequests = 0;
     105        // Reset m_resizedImage now, because we don't distinguish between the
     106        // last requested resize info and m_resizedImage's resize info.
     107        m_resizedImage.reset();
     108    }
    79109
    80 SkBitmap NativeImageSkia::resizedBitmap(int w, int h) const
    81 {
    82     if (m_resizedImage.width() != w || m_resizedImage.height() != h)
    83         m_resizedImage = skia::ImageOperations::Resize(*this, skia::ImageOperations::RESIZE_LANCZOS3, w, h);
    84 
    85     return m_resizedImage;
    86 }
    87 
    88 bool NativeImageSkia::shouldCacheResampling(int destWidth,
    89                                             int destHeight,
    90                                             int destSubsetWidth,
    91                                             int destSubsetHeight) const
    92 {
    93110    // We can not cache incomplete frames. This might be a good optimization in
    94111    // the future, were we know how much of the frame has been decoded, so when
     
    107124    // many more will be made as well, and we'll go ahead and cache it.
    108125    static const int kManyRequestThreshold = 4;
    109     if (m_lastRequestSize.width() == destWidth &&
    110         m_lastRequestSize.height() == destHeight) {
    111         if (m_resizeRequests >= kManyRequestThreshold)
    112             return true;
    113     } else {
    114         // When a different size is being requested, count this as a query
    115         // (hasResizedBitmap) and reset the counter.
    116         m_lastRequestSize = IntSize(destWidth, destHeight);
    117         m_resizeRequests = 0;
    118     }
     126    if (m_resizeRequests >= kManyRequestThreshold)
     127        return true;
    119128
    120     // Otherwise, use the heuristic that if more than 1/4 of the image is
    121     // requested, it's worth caching.
    122     int destSize = destWidth * destHeight;
    123     int destSubsetSize = destSubsetWidth * destSubsetHeight;
    124     return destSize / 4 < destSubsetSize;
     129    // If more than 1/4 of the resized image is visible, it's worth caching.
     130    int destVisibleSize = destVisibleSubset.width() * destVisibleSubset.height();
     131    return (destVisibleSize > (destWidth * destHeight) / 4);
     132}
     133
     134NativeImageSkia::CachedImageInfo::CachedImageInfo()
     135{
     136    srcSubset.setEmpty();
     137}
     138
     139bool NativeImageSkia::CachedImageInfo::isEqual(const SkIRect& otherSrcSubset, int width, int height) const
     140{
     141    return srcSubset == otherSrcSubset
     142        && requestSize.width() == width
     143        && requestSize.height() == height;
     144}
     145
     146void NativeImageSkia::CachedImageInfo::set(const SkIRect& otherSrcSubset, int width, int height)
     147{
     148    srcSubset = otherSrcSubset;
     149    requestSize.setWidth(width);
     150    requestSize.setHeight(height);
    125151}
    126152
  • trunk/Source/WebCore/platform/graphics/skia/NativeImageSkia.h

    r67412 r93580  
    3333
    3434#include "SkBitmap.h"
     35#include "SkRect.h"
    3536#include "IntSize.h"
    3637
     
    6465    // We can keep a resized version of the bitmap cached on this object.
    6566    // This function will return true if there is a cached version of the
    66     // given image subset with the given dimensions.
    67     bool hasResizedBitmap(int width, int height) const;
     67    // given image subset with the given dimensions and subsets.
     68    bool hasResizedBitmap(const SkIRect& srcSubset, int width, int height) const;
    6869
    69     // This will return an existing resized image, or generate a new one of
    70     // the specified size and store it in the cache. Subsetted images can not
    71     // be cached unless the subset is the entire bitmap.
    72     SkBitmap resizedBitmap(int width, int height) const;
     70    // This will return an existing resized image subset, or generate a new one
     71    // of the specified size and subsets and possibly cache it.
     72    // srcSubset is the subset of the image to resize in image space.
     73    SkBitmap resizedBitmap(const SkIRect& srcSubset, int destWidth, int destHeight) const
     74    {
     75        SkIRect destVisibleSubset = {0, 0, destWidth, destHeight};
     76        return resizedBitmap(srcSubset, destWidth, destHeight, destVisibleSubset);
     77    }
     78
     79    // Same as above, but returns a subset of the destination image (ie: the
     80    // visible subset). destVisibleSubset is the subset of the resized
     81    // (destWidth x destHeight) image.
     82    // In other words:
     83    // - crop image by srcSubset -> imageSubset.
     84    // - resize imageSubset to destWidth x destHeight -> destImage.
     85    // - return destImage cropped by destVisibleSubset.
     86    SkBitmap resizedBitmap(const SkIRect& srcSubset, int destWidth, int destHeight, const SkIRect& destVisibleSubset) const;
     87
     88private:
     89    // CachedImageInfo is used to uniquely identify cached or requested image
     90    // resizes.
     91    struct CachedImageInfo {
     92        IntSize requestSize;
     93        SkIRect srcSubset;
     94
     95        CachedImageInfo();
     96
     97        bool isEqual(const SkIRect& otherSrcSubset, int width, int height) const;
     98        void set(const SkIRect& otherSrcSubset, int width, int height);
     99    };
    73100
    74101    // Returns true if the given resize operation should either resize the whole
    75102    // image and cache it, or resize just the part it needs and throw the result
    76103    // away.
     104    //
     105    // Calling this function may increment a request count that can change the
     106    // result of subsequent calls.
    77107    //
    78108    // On the one hand, if only a small subset is desired, then we will waste a
     
    82112    // scrolling on and off the screen. Since we only cache when doing the
    83113    // entire thing, it's best to just do it up front.
    84     bool shouldCacheResampling(int destWidth,
     114    bool shouldCacheResampling(const SkIRect& srcSubset,
     115                               int destWidth,
    85116                               int destHeight,
    86                                int destSubsetWidth,
    87                                int destSubsetHeight) const;
     117                               const SkIRect& destSubset) const;
    88118
    89 private:
    90119    // Set to true when the data is complete. Before the entire image has
    91120    // loaded, we do not want to cache a resize.
     
    98127    // the last size.
    99128    //
    100     // Every time we get a request, if it matches the m_lastRequestSize, we'll
    101     // increment the counter, and if not, we'll reset the counter and save the
    102     // size.
     129    // Every time we get a call to shouldCacheResampling, if it matches the
     130    // m_cachedImageInfo, we'll increment the counter, and if not, we'll reset
     131    // the counter and save the dimensions.
    103132    //
    104133    // This allows us to see if many requests have been made for the same
    105134    // resized image, we know that we should probably cache it, even if all of
    106135    // those requests individually are small and would not otherwise be cached.
    107     mutable IntSize m_lastRequestSize;
     136    //
     137    // We also track the source and destination subsets for caching partial
     138    // image resizes.
     139    mutable CachedImageInfo m_cachedImageInfo;
    108140    mutable int m_resizeRequests;
    109141};
Note: See TracChangeset for help on using the changeset viewer.