CTTE Timer and DeferrableOneShotTimer
[WebKit-https.git] / Source / WebCore / rendering / ImageQualityController.cpp
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ImageQualityController.h"
28
29 #include "Frame.h"
30 #include "GraphicsContext.h"
31 #include "Page.h"
32 #include "RenderBoxModelObject.h"
33 #include "RenderView.h"
34
35 namespace WebCore {
36
37 static const double cInterpolationCutoff = 800. * 800.;
38 static const double cLowQualityTimeThreshold = 0.500; // 500 ms
39
40 ImageQualityController::ImageQualityController(const RenderView& renderView)
41     : m_renderView(renderView)
42     , m_timer(this, &ImageQualityController::highQualityRepaintTimerFired)
43     , m_animatedResizeIsActive(false)
44     , m_liveResizeOptimizationIsActive(false)
45 {
46 }
47
48 void ImageQualityController::removeLayer(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer)
49 {
50     if (!innerMap)
51         return;
52     innerMap->remove(layer);
53     if (innerMap->isEmpty())
54         removeObject(object);
55 }
56
57 void ImageQualityController::set(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer, const LayoutSize& size)
58 {
59     if (innerMap)
60         innerMap->set(layer, size);
61     else {
62         LayerSizeMap newInnerMap;
63         newInnerMap.set(layer, size);
64         m_objectLayerSizeMap.set(object, newInnerMap);
65     }
66 }
67
68 void ImageQualityController::removeObject(RenderBoxModelObject* object)
69 {
70     m_objectLayerSizeMap.remove(object);
71     if (m_objectLayerSizeMap.isEmpty()) {
72         m_animatedResizeIsActive = false;
73         m_timer.stop();
74     }
75 }
76
77 void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityController>&)
78 {
79     if (m_renderView.documentBeingDestroyed())
80         return;
81     if (!m_animatedResizeIsActive && !m_liveResizeOptimizationIsActive)
82         return;
83     m_animatedResizeIsActive = false;
84
85     // If the FrameView is in live resize, punt the timer and hold back for now.
86     if (m_renderView.frameView().inLiveResize()) {
87         restartTimer();
88         return;
89     }
90
91     for (auto it = m_objectLayerSizeMap.begin(), end = m_objectLayerSizeMap.end(); it != end; ++it)
92         it->key->repaint();
93
94     m_liveResizeOptimizationIsActive = false;
95 }
96
97 void ImageQualityController::restartTimer()
98 {
99     m_timer.startOneShot(cLowQualityTimeThreshold);
100 }
101
102 bool ImageQualityController::shouldPaintAtLowQuality(GraphicsContext* context, RenderBoxModelObject* object, Image* image, const void *layer, const LayoutSize& size)
103 {
104     // If the image is not a bitmap image, then none of this is relevant and we just paint at high
105     // quality.
106     if (!image || !(image->isBitmapImage() || image->isPDFDocumentImage()) || context->paintingDisabled())
107         return false;
108
109     switch (object->style().imageRendering()) {
110     case ImageRenderingOptimizeSpeed:
111     case ImageRenderingCrispEdges:
112         return true;
113     case ImageRenderingOptimizeQuality:
114         return false;
115     case ImageRenderingAuto:
116         break;
117     }
118
119     // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image
120     // is actually being scaled.
121     IntSize imageSize(image->width(), image->height());
122
123     // Look ourselves up in the hashtables.
124     auto i = m_objectLayerSizeMap.find(object);
125     LayerSizeMap* innerMap = i != m_objectLayerSizeMap.end() ? &i->value : 0;
126     LayoutSize oldSize;
127     bool isFirstResize = true;
128     if (innerMap) {
129         LayerSizeMap::iterator j = innerMap->find(layer);
130         if (j != innerMap->end()) {
131             isFirstResize = false;
132             oldSize = j->value;
133         }
134     }
135
136     // If the containing FrameView is being resized, paint at low quality until resizing is finished.
137     if (Frame* frame = object->document().frame()) {
138         bool frameViewIsCurrentlyInLiveResize = frame->view() && frame->view()->inLiveResize();
139         if (frameViewIsCurrentlyInLiveResize) {
140             set(object, innerMap, layer, size);
141             restartTimer();
142             m_liveResizeOptimizationIsActive = true;
143             return true;
144         }
145         if (m_liveResizeOptimizationIsActive)
146             return false;
147     }
148
149     const AffineTransform& currentTransform = context->getCTM();
150     bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped();
151     if (!contextIsScaled && size == imageSize) {
152         // There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list.
153         removeLayer(object, innerMap, layer);
154         return false;
155     }
156
157     // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case.
158     if (m_renderView.frame().page()->inLowQualityImageInterpolationMode()) {
159         double totalPixels = static_cast<double>(image->width()) * static_cast<double>(image->height());
160         if (totalPixels > cInterpolationCutoff)
161             return true;
162     }
163
164     // If an animated resize is active, paint in low quality and kick the timer ahead.
165     if (m_animatedResizeIsActive) {
166         set(object, innerMap, layer, size);
167         restartTimer();
168         return true;
169     }
170     // If this is the first time resizing this image, or its size is the
171     // same as the last resize, draw at high res, but record the paint
172     // size and set the timer.
173     if (isFirstResize || oldSize == size) {
174         restartTimer();
175         set(object, innerMap, layer, size);
176         return false;
177     }
178     // If the timer is no longer active, draw at high quality and don't
179     // set the timer.
180     if (!m_timer.isActive()) {
181         removeLayer(object, innerMap, layer);
182         return false;
183     }
184     // This object has been resized to two different sizes while the timer
185     // is active, so draw at low quality, set the flag for animated resizes and
186     // the object to the list for high quality redraw.
187     set(object, innerMap, layer, size);
188     m_animatedResizeIsActive = true;
189     restartTimer();
190     return true;
191 }
192
193 }