Combine event and touch action regions into a single class
[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 #include <wtf/Optional.h>
35
36 namespace WebCore {
37
38 static const double cInterpolationCutoff = 800. * 800.;
39 static const Seconds lowQualityTimeThreshold { 500_ms };
40
41 ImageQualityController::ImageQualityController(const RenderView& renderView)
42     : m_renderView(renderView)
43     , m_timer(*this, &ImageQualityController::highQualityRepaintTimerFired)
44 {
45 }
46
47 void ImageQualityController::removeLayer(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer)
48 {
49     if (!innerMap)
50         return;
51     innerMap->remove(layer);
52     if (innerMap->isEmpty())
53         removeObject(object);
54 }
55
56 void ImageQualityController::set(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer, const LayoutSize& size)
57 {
58     if (innerMap)
59         innerMap->set(layer, size);
60     else {
61         LayerSizeMap newInnerMap;
62         newInnerMap.set(layer, size);
63         m_objectLayerSizeMap.set(object, newInnerMap);
64     }
65 }
66
67 void ImageQualityController::removeObject(RenderBoxModelObject* object)
68 {
69     m_objectLayerSizeMap.remove(object);
70     if (m_objectLayerSizeMap.isEmpty()) {
71         m_animatedResizeIsActive = false;
72         m_timer.stop();
73     }
74 }
75
76 void ImageQualityController::highQualityRepaintTimerFired()
77 {
78     if (m_renderView.renderTreeBeingDestroyed())
79         return;
80     if (!m_animatedResizeIsActive && !m_liveResizeOptimizationIsActive)
81         return;
82     m_animatedResizeIsActive = false;
83
84     // If the FrameView is in live resize, punt the timer and hold back for now.
85     if (m_renderView.frameView().inLiveResize()) {
86         restartTimer();
87         return;
88     }
89
90     for (auto it = m_objectLayerSizeMap.begin(), end = m_objectLayerSizeMap.end(); it != end; ++it)
91         it->key->repaint();
92
93     m_liveResizeOptimizationIsActive = false;
94 }
95
96 void ImageQualityController::restartTimer()
97 {
98     m_timer.startOneShot(lowQualityTimeThreshold);
99 }
100
101 Optional<InterpolationQuality> ImageQualityController::interpolationQualityFromStyle(const RenderStyle& style)
102 {
103     switch (style.imageRendering()) {
104     case ImageRendering::OptimizeSpeed:
105         return InterpolationLow;
106     case ImageRendering::CrispEdges:
107     case ImageRendering::Pixelated:
108         return InterpolationNone;
109     case ImageRendering::OptimizeQuality:
110         return InterpolationDefault; // FIXME: CSS 3 Images says that optimizeQuality should behave like 'auto', but that prevents authors from overriding this low quality rendering behavior.
111     case ImageRendering::Auto:
112         break;
113     }
114     return WTF::nullopt;
115 }
116
117 InterpolationQuality ImageQualityController::chooseInterpolationQuality(GraphicsContext& context, RenderBoxModelObject* object, Image& image, const void* layer, const LayoutSize& size)
118 {
119     // If the image is not a bitmap image, then none of this is relevant and we just paint at high quality.
120     if (!(image.isBitmapImage() || image.isPDFDocumentImage()) || context.paintingDisabled())
121         return InterpolationDefault;
122
123     if (Optional<InterpolationQuality> styleInterpolation = interpolationQualityFromStyle(object->style()))
124         return styleInterpolation.value();
125
126     // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image
127     // is actually being scaled.
128     IntSize imageSize(image.width(), image.height());
129
130     // Look ourselves up in the hashtables.
131     auto i = m_objectLayerSizeMap.find(object);
132     LayerSizeMap* innerMap = i != m_objectLayerSizeMap.end() ? &i->value : 0;
133     LayoutSize oldSize;
134     bool isFirstResize = true;
135     if (innerMap) {
136         LayerSizeMap::iterator j = innerMap->find(layer);
137         if (j != innerMap->end()) {
138             isFirstResize = false;
139             oldSize = j->value;
140         }
141     }
142
143     // If the containing FrameView is being resized, paint at low quality until resizing is finished.
144     if (Frame* frame = object->document().frame()) {
145         bool frameViewIsCurrentlyInLiveResize = frame->view() && frame->view()->inLiveResize();
146         if (frameViewIsCurrentlyInLiveResize) {
147             set(object, innerMap, layer, size);
148             restartTimer();
149             m_liveResizeOptimizationIsActive = true;
150             return InterpolationLow;
151         }
152         if (m_liveResizeOptimizationIsActive)
153             return InterpolationDefault;
154     }
155
156     const AffineTransform& currentTransform = context.getCTM();
157     bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped();
158     if (!contextIsScaled && size == imageSize) {
159         // There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list.
160         removeLayer(object, innerMap, layer);
161         return InterpolationDefault;
162     }
163
164     // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case.
165     if (m_renderView.page().inLowQualityImageInterpolationMode()) {
166         double totalPixels = static_cast<double>(image.width()) * static_cast<double>(image.height());
167         if (totalPixels > cInterpolationCutoff)
168             return InterpolationLow;
169     }
170
171     // If an animated resize is active, paint in low quality and kick the timer ahead.
172     if (m_animatedResizeIsActive) {
173         set(object, innerMap, layer, size);
174         restartTimer();
175         return InterpolationLow;
176     }
177     // If this is the first time resizing this image, or its size is the
178     // same as the last resize, draw at high res, but record the paint
179     // size and set the timer.
180     if (isFirstResize || oldSize == size) {
181         restartTimer();
182         set(object, innerMap, layer, size);
183         return InterpolationDefault;
184     }
185     // If the timer is no longer active, draw at high quality and don't
186     // set the timer.
187     if (!m_timer.isActive()) {
188         removeLayer(object, innerMap, layer);
189         return InterpolationDefault;
190     }
191     // This object has been resized to two different sizes while the timer
192     // is active, so draw at low quality, set the flag for animated resizes and
193     // the object to the list for high quality redraw.
194     set(object, innerMap, layer, size);
195     m_animatedResizeIsActive = true;
196     restartTimer();
197     return InterpolationLow;
198 }
199
200 }