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
36namespace WebCore {
37
38static const double cInterpolationCutoff = 800. * 800.;
39static const Seconds lowQualityTimeThreshold { 500_ms };
40
41ImageQualityController::ImageQualityController(const RenderView& renderView)
42 : m_renderView(renderView)
43 , m_timer(*this, &ImageQualityController::highQualityRepaintTimerFired)
44{
45}
46
47void 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
56void 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
67void 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
76void 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
96void ImageQualityController::restartTimer()
97{
98 m_timer.startOneShot(lowQualityTimeThreshold);
99}
100
101Optional<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
117InterpolationQuality 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}
201