1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "core/layout/ImageQualityController.h"
8 #include "core/layout/LayoutImage.h"
9 #include "core/layout/LayoutTestHelper.h"
10 #include "platform/graphics/GraphicsContext.h"
11 #include "platform/graphics/paint/DisplayItemList.h"
13 #include <gtest/gtest.h>
17 class ImageQualityControllerTest
: public RenderingTest
{
19 ImageQualityController
* controller() { return m_controller
; }
24 m_controller
= ImageQualityController::imageQualityController();
25 RenderingTest::SetUp();
27 void TearDown() override
30 ImageQualityController
* m_controller
;
33 TEST_F(ImageQualityControllerTest
, RegularImage
)
35 setBodyInnerHTML("<img src='myimage'></img>");
36 LayoutObject
* obj
= document().body()->firstChild()->layoutObject();
38 EXPECT_EQ(InterpolationDefault
, controller()->chooseInterpolationQuality(nullptr, obj
, nullptr, nullptr, LayoutSize()));
41 TEST_F(ImageQualityControllerTest
, ImageRenderingPixelated
)
43 setBodyInnerHTML("<img src='myimage' style='image-rendering: pixelated'></img>");
44 LayoutObject
* obj
= document().body()->firstChild()->layoutObject();
46 EXPECT_EQ(InterpolationNone
, controller()->chooseInterpolationQuality(nullptr, obj
, nullptr, nullptr, LayoutSize()));
49 #if !USE(LOW_QUALITY_IMAGE_INTERPOLATION)
51 class TestImageAnimated
: public Image
{
53 bool maybeAnimated() override
{ return true; }
54 bool currentFrameKnownToBeOpaque() override
{ return false; }
55 IntSize
size() const override
{ return IntSize(); }
56 void destroyDecodedData(bool) override
{ }
57 void draw(SkCanvas
*, const SkPaint
&, const FloatRect
& dstRect
, const FloatRect
& srcRect
, RespectImageOrientationEnum
, ImageClampingMode
) override
{ }
58 PassRefPtr
<SkImage
> imageForCurrentFrame() override
{ return nullptr; }
61 TEST_F(ImageQualityControllerTest
, ImageMaybeAnimated
)
63 setBodyInnerHTML("<img src='myimage'></img>");
64 LayoutImage
* img
= toLayoutImage(document().body()->firstChild()->layoutObject());
66 RefPtr
<TestImageAnimated
> testImage
= adoptRef(new TestImageAnimated
);
67 EXPECT_EQ(InterpolationMedium
, controller()->chooseInterpolationQuality(nullptr, img
, testImage
.get(), nullptr, LayoutSize()));
70 class TestImageWithContrast
: public Image
{
72 bool maybeAnimated() override
{ return true; }
73 bool currentFrameKnownToBeOpaque() override
{ return false; }
74 IntSize
size() const override
{ return IntSize(); }
75 void destroyDecodedData(bool) override
{ }
76 void draw(SkCanvas
*, const SkPaint
&, const FloatRect
& dstRect
, const FloatRect
& srcRect
, RespectImageOrientationEnum
, ImageClampingMode
) override
{ }
78 bool isBitmapImage() const override
{ return true; }
79 PassRefPtr
<SkImage
> imageForCurrentFrame() override
{ return nullptr; }
82 TEST_F(ImageQualityControllerTest
, LowQualityFilterForContrast
)
84 setBodyInnerHTML("<img src='myimage' style='image-rendering: -webkit-optimize-contrast'></img>");
85 LayoutImage
* img
= toLayoutImage(document().body()->firstChild()->layoutObject());
87 RefPtr
<TestImageWithContrast
> testImage
= adoptRef(new TestImageWithContrast
);
88 EXPECT_EQ(InterpolationLow
, controller()->chooseInterpolationQuality(nullptr, img
, testImage
.get(), testImage
.get(), LayoutSize()));
91 class TestImageLowQuality
: public Image
{
93 bool maybeAnimated() override
{ return true; }
94 bool currentFrameKnownToBeOpaque() override
{ return false; }
95 IntSize
size() const override
{ return IntSize(1, 1); }
96 void destroyDecodedData(bool) override
{ }
97 void draw(SkCanvas
*, const SkPaint
&, const FloatRect
& dstRect
, const FloatRect
& srcRect
, RespectImageOrientationEnum
, ImageClampingMode
) override
{ }
99 bool isBitmapImage() const override
{ return true; }
100 PassRefPtr
<SkImage
> imageForCurrentFrame() override
{ return nullptr; }
103 TEST_F(ImageQualityControllerTest
, MediumQualityFilterForUnscaledImage
)
105 setBodyInnerHTML("<img src='myimage'></img>");
106 LayoutImage
* img
= toLayoutImage(document().body()->firstChild()->layoutObject());
108 RefPtr
<TestImageLowQuality
> testImage
= adoptRef(new TestImageLowQuality
);
109 OwnPtr
<DisplayItemList
> displayItemList
= DisplayItemList::create();
110 GraphicsContext
context(displayItemList
.get());
111 EXPECT_EQ(InterpolationMedium
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(1, 1)));
114 class MockTimer
: public Timer
<ImageQualityController
> {
115 typedef void (ImageQualityController::*TimerFiredFunction
)(Timer
*);
117 MockTimer(ImageQualityController
* o
, TimerFiredFunction f
)
118 : Timer
<ImageQualityController
>(o
, f
)
124 this->Timer
<ImageQualityController
>::fired();
129 TEST_F(ImageQualityControllerTest
, LowQualityFilterForLiveResize
)
131 MockTimer
* mockTimer
= new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired
);
132 controller()->setTimer(mockTimer
);
133 setBodyInnerHTML("<img src='myimage'></img>");
134 LayoutImage
* img
= toLayoutImage(document().body()->firstChild()->layoutObject());
136 RefPtr
<TestImageLowQuality
> testImage
= adoptRef(new TestImageLowQuality
);
137 OwnPtr
<DisplayItemList
> displayItemList
= DisplayItemList::create();
138 GraphicsContext
context(displayItemList
.get());
141 document().frame()->view()->willStartLiveResize();
142 EXPECT_EQ(InterpolationLow
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(2, 2)));
144 document().frame()->view()->willEndLiveResize();
146 // End of live resize, but timer has not fired. Therefore paint at non-low quality.
147 EXPECT_EQ(InterpolationMedium
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(3, 3)));
149 // Start another resize
150 document().frame()->view()->willStartLiveResize();
151 EXPECT_EQ(InterpolationLow
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(3, 3)));
153 // While still in resize, expire the timer.
154 document().frame()->view()->willEndLiveResize();
157 // End of live resize, and timer has fired. Therefore paint at non-low quality, even though the size has changed.
158 EXPECT_EQ(InterpolationMedium
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(4, 4)));
161 TEST_F(ImageQualityControllerTest
, LowQualityFilterForResizingImage
)
163 MockTimer
* mockTimer
= new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired
);
164 controller()->setTimer(mockTimer
);
165 setBodyInnerHTML("<img src='myimage'></img>");
166 LayoutImage
* img
= toLayoutImage(document().body()->firstChild()->layoutObject());
168 RefPtr
<TestImageLowQuality
> testImage
= adoptRef(new TestImageLowQuality
);
169 OwnPtr
<DisplayItemList
> displayItemList
= DisplayItemList::create();
170 GraphicsContext
context(displayItemList
.get());
172 // Paint once. This will kick off a timer to see if we resize it during that timer's execution.
173 EXPECT_EQ(InterpolationMedium
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(2, 2)));
175 // Go into low-quality mode now that the size changed.
176 EXPECT_EQ(InterpolationLow
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(3, 3)));
178 // Stay in low-quality mode since the size changed again.
179 EXPECT_EQ(InterpolationLow
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(4, 4)));
182 // The timer fired before painting at another size, so this doesn't count as animation. Therefore not painting at low quality.
183 EXPECT_EQ(InterpolationMedium
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(4, 4)));
186 TEST_F(ImageQualityControllerTest
, DontKickTheAnimationTimerWhenPaintingAtTheSameSize
)
188 MockTimer
* mockTimer
= new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired
);
189 controller()->setTimer(mockTimer
);
190 setBodyInnerHTML("<img src='myimage'></img>");
191 LayoutImage
* img
= toLayoutImage(document().body()->firstChild()->layoutObject());
193 RefPtr
<TestImageLowQuality
> testImage
= adoptRef(new TestImageLowQuality
);
194 OwnPtr
<DisplayItemList
> displayItemList
= DisplayItemList::create();
195 GraphicsContext
context(displayItemList
.get());
197 // Paint once. This will kick off a timer to see if we resize it during that timer's execution.
198 EXPECT_EQ(InterpolationMedium
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(2, 2)));
200 // Go into low-quality mode now that the size changed.
201 EXPECT_EQ(InterpolationLow
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(3, 3)));
203 // Stay in low-quality mode since the size changed again.
204 EXPECT_EQ(InterpolationLow
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(4, 4)));
207 EXPECT_FALSE(mockTimer
->isActive());
208 // Painted at the same size, so even though timer is still executing, don't go to low quality.
209 EXPECT_EQ(InterpolationLow
, controller()->chooseInterpolationQuality(&context
, img
, testImage
.get(), testImage
.get(), LayoutSize(4, 4)));
210 // Check that the timer was not kicked. It should not have been, since the image was painted at the same size as last time.
211 EXPECT_FALSE(mockTimer
->isActive());