Commit 066567c2 authored by chrishtr's avatar chrishtr Committed by Commit bot

Don't use a low-quality filter when painting images while others are animating.

Previously, any image animating would trigger low-quality filters for all
images during the animation timer window. Now only do so for images that are
actually animating.

BUG=583238

Review URL: https://codereview.chromium.org/1685933002

Cr-Commit-Position: refs/heads/master@{#374738}
parent e5681355
......@@ -89,7 +89,6 @@ ImageQualityController::~ImageQualityController()
ImageQualityController::ImageQualityController()
: m_timer(adoptPtr(new Timer<ImageQualityController>(this, &ImageQualityController::highQualityRepaintTimerFired)))
, m_animatedResizeIsActive(false)
, m_liveResizeOptimizationIsActive(false)
{
}
......@@ -108,14 +107,16 @@ void ImageQualityController::removeLayer(const LayoutObject& object, LayerSizeMa
}
}
void ImageQualityController::set(const LayoutObject& object, LayerSizeMap* innerMap, const void* layer, const LayoutSize& size)
void ImageQualityController::set(const LayoutObject& object, LayerSizeMap* innerMap, const void* layer, const LayoutSize& size, bool isResizing)
{
if (innerMap) {
innerMap->set(layer, size);
m_objectLayerSizeMap.find(&object)->value.isResizing = isResizing;
} else {
LayerSizeMap newInnerMap;
newInnerMap.set(layer, size);
m_objectLayerSizeMap.set(&object, newInnerMap);
ObjectResizeInfo newResizeInfo;
newResizeInfo.layerSizeMap.set(layer, size);
newResizeInfo.isResizing = isResizing;
m_objectLayerSizeMap.set(&object, newResizeInfo);
}
}
......@@ -123,17 +124,12 @@ void ImageQualityController::objectDestroyed(const LayoutObject& object)
{
m_objectLayerSizeMap.remove(&object);
if (m_objectLayerSizeMap.isEmpty()) {
m_animatedResizeIsActive = false;
m_timer->stop();
}
}
void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityController>*)
{
if (!m_animatedResizeIsActive && !m_liveResizeOptimizationIsActive)
return;
m_animatedResizeIsActive = false;
for (auto* layoutObject : m_objectLayerSizeMap.keys()) {
if (LocalFrame* frame = layoutObject->document().frame()) {
// If this layoutObject's containing FrameView is in live resize, punt the timer and hold back for now.
......@@ -142,8 +138,15 @@ void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityCont
return;
}
}
// TODO(wangxianzhu): Use LayoutObject::mutableForPainting().
const_cast<LayoutObject*>(layoutObject)->setShouldDoFullPaintInvalidation();
ObjectLayerSizeMap::iterator i = m_objectLayerSizeMap.find(layoutObject);
if (i != m_objectLayerSizeMap.end()) {
// Only invalidate the object if it is animating.
if (i->value.isResizing) {
// TODO(wangxianzhu): Use LayoutObject::mutableForPainting().
const_cast<LayoutObject*>(layoutObject)->setShouldDoFullPaintInvalidation();
}
i->value.isResizing = false;
}
}
m_liveResizeOptimizationIsActive = false;
......@@ -169,7 +172,12 @@ bool ImageQualityController::shouldPaintAtLowQuality(const LayoutObject& object,
// Look ourselves up in the hashtables.
ObjectLayerSizeMap::iterator i = m_objectLayerSizeMap.find(&object);
LayerSizeMap* innerMap = i != m_objectLayerSizeMap.end() ? &i->value : 0;
LayerSizeMap* innerMap = nullptr;
bool objectIsResizing = false;
if (i != m_objectLayerSizeMap.end()) {
innerMap = &i->value.layerSizeMap;
objectIsResizing = i->value.isResizing;
}
LayoutSize oldSize;
bool isFirstResize = true;
if (innerMap) {
......@@ -184,7 +192,7 @@ bool ImageQualityController::shouldPaintAtLowQuality(const LayoutObject& object,
if (LocalFrame* frame = object.document().frame()) {
bool frameViewIsCurrentlyInLiveResize = frame->view() && frame->view()->inLiveResize();
if (frameViewIsCurrentlyInLiveResize) {
set(object, innerMap, layer, layoutSize);
set(object, innerMap, layer, layoutSize, true);
restartTimer();
m_liveResizeOptimizationIsActive = true;
return true;
......@@ -202,10 +210,11 @@ bool ImageQualityController::shouldPaintAtLowQuality(const LayoutObject& object,
return false;
}
// If an animated resize is active, paint in low quality and kick the timer ahead.
if (m_animatedResizeIsActive) {
set(object, innerMap, layer, layoutSize);
if (oldSize != layoutSize)
// If an animated resize is active for this object, paint in low quality and kick the timer ahead.
if (objectIsResizing) {
bool sizesChanged = oldSize != layoutSize;
set(object, innerMap, layer, layoutSize, sizesChanged);
if (sizesChanged)
restartTimer();
return true;
}
......@@ -214,7 +223,7 @@ bool ImageQualityController::shouldPaintAtLowQuality(const LayoutObject& object,
// size and set the timer.
if (isFirstResize || oldSize == layoutSize) {
restartTimer();
set(object, innerMap, layer, layoutSize);
set(object, innerMap, layer, layoutSize, false);
return false;
}
// If the timer is no longer active, draw at high quality and don't
......@@ -223,12 +232,10 @@ bool ImageQualityController::shouldPaintAtLowQuality(const LayoutObject& object,
removeLayer(object, innerMap, layer);
return false;
}
// This object has been resized to two different sizes while the timer
// is active, so draw at low quality, set the flag for animated resizes and
// the object to the list for high quality redraw.
set(object, innerMap, layer, layoutSize);
m_animatedResizeIsActive = true;
set(object, innerMap, layer, layoutSize, true);
restartTimer();
return true;
}
......
......@@ -44,7 +44,13 @@
namespace blink {
typedef HashMap<const void*, LayoutSize> LayerSizeMap;
typedef HashMap<const LayoutObject*, LayerSizeMap> ObjectLayerSizeMap;
struct ObjectResizeInfo {
LayerSizeMap layerSizeMap;
bool isResizing;
};
typedef HashMap<const LayoutObject*, ObjectResizeInfo> ObjectLayerSizeMap;
class CORE_EXPORT ImageQualityController final {
WTF_MAKE_NONCOPYABLE(ImageQualityController); USING_FAST_MALLOC(ImageQualityController);
......@@ -61,7 +67,7 @@ private:
ImageQualityController();
static bool has(const LayoutObject&);
void set(const LayoutObject&, LayerSizeMap* innerMap, const void* layer, const LayoutSize&);
void set(const LayoutObject&, LayerSizeMap* innerMap, const void* layer, const LayoutSize&, bool isResizing);
bool shouldPaintAtLowQuality(const LayoutObject&, Image*, const void* layer, const LayoutSize&);
void removeLayer(const LayoutObject&, LayerSizeMap* innerMap, const void* layer);
......@@ -76,7 +82,6 @@ private:
ObjectLayerSizeMap m_objectLayerSizeMap;
OwnPtr<Timer<ImageQualityController>> m_timer;
bool m_animatedResizeIsActive;
bool m_liveResizeOptimizationIsActive;
// For calling set().
......@@ -85,6 +90,7 @@ private:
// For calling setTimer(),
FRIEND_TEST_ALL_PREFIXES(ImageQualityControllerTest, LowQualityFilterForLiveResize);
FRIEND_TEST_ALL_PREFIXES(ImageQualityControllerTest, LowQualityFilterForResizingImage);
FRIEND_TEST_ALL_PREFIXES(ImageQualityControllerTest, MediumQualityFilterForNotAnimatedWhileAnotherAnimates);
FRIEND_TEST_ALL_PREFIXES(ImageQualityControllerTest, DontKickTheAnimationTimerWhenPaintingAtTheSameSize);
};
......
......@@ -177,6 +177,36 @@ TEST_F(ImageQualityControllerTest, LowQualityFilterForResizingImage)
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
}
TEST_F(ImageQualityControllerTest, MediumQualityFilterForNotAnimatedWhileAnotherAnimates)
{
MockTimer* mockTimer = new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired);
controller()->setTimer(mockTimer);
setBodyInnerHTML("<img id='myAnimatingImage' src='myimage'></img> <img id='myNonAnimatingImage' src='myimage2'></img>");
LayoutImage* animatingImage = toLayoutImage(document().getElementById("myAnimatingImage")->layoutObject());
LayoutImage* nonAnimatingImage = toLayoutImage(document().getElementById("myNonAnimatingImage")->layoutObject());
RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);
OwnPtr<PaintController> paintController = PaintController::create();
GraphicsContext context(*paintController);
// Paint once. This will kick off a timer to see if we resize it during that timer's execution.
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*animatingImage, testImage.get(), testImage.get(), LayoutSize(2, 2)));
// Go into low-quality mode now that the size changed.
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*animatingImage, testImage.get(), testImage.get(), LayoutSize(3, 3)));
// The non-animating image receives a medium-quality filter, even though the other one is animating.
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*nonAnimatingImage, testImage.get(), testImage.get(), LayoutSize(4, 4)));
// Now the second image has animated, so it also gets painted with a low-quality filter.
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*nonAnimatingImage, testImage.get(), testImage.get(), LayoutSize(3, 3)));
mockTimer->fire();
// The timer fired before painting at another size, so this doesn't count as animation. Therefore not painting at low quality for any image.
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*animatingImage, testImage.get(), testImage.get(), LayoutSize(4, 4)));
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*nonAnimatingImage, testImage.get(), testImage.get(), LayoutSize(4, 4)));
}
TEST_F(ImageQualityControllerTest, DontKickTheAnimationTimerWhenPaintingAtTheSameSize)
{
MockTimer* mockTimer = new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired);
......
......@@ -26,7 +26,7 @@ TEST_F(LayoutPartTest, DestroyUpdatesImageQualityController)
RefPtrWillBeRawPtr<Element> element = HTMLElement::create(HTMLNames::divTag, document());
LayoutObject* part = new OverriddenLayoutPart(element.get());
// The third and forth arguments are not important in this test.
ImageQualityController::imageQualityController()->set(*part, 0, this, LayoutSize(1, 1));
ImageQualityController::imageQualityController()->set(*part, 0, this, LayoutSize(1, 1), false);
EXPECT_TRUE(ImageQualityController::has(*part));
part->destroy();
EXPECT_FALSE(ImageQualityController::has(*part));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment