blob: e70a6f4b6044599f2d8f067e783cec9e7df6bfe0 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "core/layout/ImageQualityController.h"
#include "core/layout/LayoutImage.h"
#include "core/layout/LayoutTestHelper.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/paint/DisplayItemList.h"
#include <gtest/gtest.h>
namespace blink {
class ImageQualityControllerTest : public RenderingTest {
protected:
ImageQualityController* controller() { return m_controller; }
private:
void SetUp() override
{
m_controller = ImageQualityController::imageQualityController();
RenderingTest::SetUp();
}
void TearDown() override
{
}
ImageQualityController* m_controller;
};
TEST_F(ImageQualityControllerTest, RegularImage)
{
setBodyInnerHTML("<img src='myimage'></img>");
LayoutObject* obj = document().body()->firstChild()->layoutObject();
EXPECT_EQ(InterpolationDefault, controller()->chooseInterpolationQuality(nullptr, obj, nullptr, nullptr, LayoutSize()));
}
TEST_F(ImageQualityControllerTest, ImageRenderingPixelated)
{
setBodyInnerHTML("<img src='myimage' style='image-rendering: pixelated'></img>");
LayoutObject* obj = document().body()->firstChild()->layoutObject();
EXPECT_EQ(InterpolationNone, controller()->chooseInterpolationQuality(nullptr, obj, nullptr, nullptr, LayoutSize()));
}
#if !USE(LOW_QUALITY_IMAGE_INTERPOLATION)
class TestImageAnimated : public Image {
public:
bool maybeAnimated() override { return true; }
bool currentFrameKnownToBeOpaque() override { return false; }
IntSize size() const override { return IntSize(); }
void destroyDecodedData(bool) override { }
void draw(SkCanvas*, const SkPaint&, const FloatRect& dstRect, const FloatRect& srcRect, RespectImageOrientationEnum, ImageClampingMode) override { }
PassRefPtr<SkImage> imageForCurrentFrame() override { return nullptr; }
};
TEST_F(ImageQualityControllerTest, ImageMaybeAnimated)
{
setBodyInnerHTML("<img src='myimage'></img>");
LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());
RefPtr<TestImageAnimated> testImage = adoptRef(new TestImageAnimated);
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(nullptr, img, testImage.get(), nullptr, LayoutSize()));
}
class TestImageWithContrast : public Image {
public:
bool maybeAnimated() override { return true; }
bool currentFrameKnownToBeOpaque() override { return false; }
IntSize size() const override { return IntSize(); }
void destroyDecodedData(bool) override { }
void draw(SkCanvas*, const SkPaint&, const FloatRect& dstRect, const FloatRect& srcRect, RespectImageOrientationEnum, ImageClampingMode) override { }
bool isBitmapImage() const override { return true; }
PassRefPtr<SkImage> imageForCurrentFrame() override { return nullptr; }
};
TEST_F(ImageQualityControllerTest, LowQualityFilterForContrast)
{
setBodyInnerHTML("<img src='myimage' style='image-rendering: -webkit-optimize-contrast'></img>");
LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());
RefPtr<TestImageWithContrast> testImage = adoptRef(new TestImageWithContrast);
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(nullptr, img, testImage.get(), testImage.get(), LayoutSize()));
}
class TestImageLowQuality : public Image {
public:
bool maybeAnimated() override { return true; }
bool currentFrameKnownToBeOpaque() override { return false; }
IntSize size() const override { return IntSize(1, 1); }
void destroyDecodedData(bool) override { }
void draw(SkCanvas*, const SkPaint&, const FloatRect& dstRect, const FloatRect& srcRect, RespectImageOrientationEnum, ImageClampingMode) override { }
bool isBitmapImage() const override { return true; }
PassRefPtr<SkImage> imageForCurrentFrame() override { return nullptr; }
};
TEST_F(ImageQualityControllerTest, MediumQualityFilterForUnscaledImage)
{
setBodyInnerHTML("<img src='myimage'></img>");
LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());
RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);
OwnPtr<DisplayItemList> displayItemList = DisplayItemList::create();
GraphicsContext context(displayItemList.get());
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(1, 1)));
}
class MockTimer : public Timer<ImageQualityController> {
typedef void (ImageQualityController::*TimerFiredFunction)(Timer*);
public:
MockTimer(ImageQualityController* o, TimerFiredFunction f)
: Timer<ImageQualityController>(o, f)
{
}
void fire()
{
this->Timer<ImageQualityController>::fired();
stop();
}
};
TEST_F(ImageQualityControllerTest, LowQualityFilterForLiveResize)
{
MockTimer* mockTimer = new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired);
controller()->setTimer(mockTimer);
setBodyInnerHTML("<img src='myimage'></img>");
LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());
RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);
OwnPtr<DisplayItemList> displayItemList = DisplayItemList::create();
GraphicsContext context(displayItemList.get());
// Start a resize
document().frame()->view()->willStartLiveResize();
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(2, 2)));
document().frame()->view()->willEndLiveResize();
// End of live resize, but timer has not fired. Therefore paint at non-low quality.
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(3, 3)));
// Start another resize
document().frame()->view()->willStartLiveResize();
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(3, 3)));
// While still in resize, expire the timer.
document().frame()->view()->willEndLiveResize();
mockTimer->fire();
// End of live resize, and timer has fired. Therefore paint at non-low quality, even though the size has changed.
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
}
TEST_F(ImageQualityControllerTest, LowQualityFilterForResizingImage)
{
MockTimer* mockTimer = new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired);
controller()->setTimer(mockTimer);
setBodyInnerHTML("<img src='myimage'></img>");
LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());
RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);
OwnPtr<DisplayItemList> displayItemList = DisplayItemList::create();
GraphicsContext context(displayItemList.get());
// Paint once. This will kick off a timer to see if we resize it during that timer's execution.
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(2, 2)));
// Go into low-quality mode now that the size changed.
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(3, 3)));
// Stay in low-quality mode since the size changed again.
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
mockTimer->fire();
// The timer fired before painting at another size, so this doesn't count as animation. Therefore not painting at low quality.
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
}
TEST_F(ImageQualityControllerTest, DontKickTheAnimationTimerWhenPaintingAtTheSameSize)
{
MockTimer* mockTimer = new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired);
controller()->setTimer(mockTimer);
setBodyInnerHTML("<img src='myimage'></img>");
LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());
RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);
OwnPtr<DisplayItemList> displayItemList = DisplayItemList::create();
GraphicsContext context(displayItemList.get());
// Paint once. This will kick off a timer to see if we resize it during that timer's execution.
EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(2, 2)));
// Go into low-quality mode now that the size changed.
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(3, 3)));
// Stay in low-quality mode since the size changed again.
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
mockTimer->stop();
EXPECT_FALSE(mockTimer->isActive());
// Painted at the same size, so even though timer is still executing, don't go to low quality.
EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(&context, img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
// Check that the timer was not kicked. It should not have been, since the image was painted at the same size as last time.
EXPECT_FALSE(mockTimer->isActive());
}
#endif
} // namespace blink