1 /* 2 * Copyright (C) 2013 Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "core/rendering/ImageQualityController.h" 33 34 #include "core/frame/FrameView.h" 35 #include "core/frame/LocalFrame.h" 36 #include "platform/graphics/GraphicsContext.h" 37 38 namespace blink { 39 40 static const double cLowQualityTimeThreshold = 0.500; // 500 ms 41 42 static ImageQualityController* gImageQualityController = 0; 43 44 ImageQualityController* ImageQualityController::imageQualityController() 45 { 46 if (!gImageQualityController) 47 gImageQualityController = new ImageQualityController; 48 49 return gImageQualityController; 50 } 51 52 void ImageQualityController::remove(RenderObject* renderer) 53 { 54 if (gImageQualityController) { 55 gImageQualityController->objectDestroyed(renderer); 56 if (gImageQualityController->isEmpty()) { 57 delete gImageQualityController; 58 gImageQualityController = 0; 59 } 60 } 61 } 62 63 bool ImageQualityController::has(RenderObject* renderer) 64 { 65 return gImageQualityController && gImageQualityController->m_objectLayerSizeMap.contains(renderer); 66 } 67 68 InterpolationQuality ImageQualityController::chooseInterpolationQuality(GraphicsContext* context, RenderObject* object, Image* image, const void* layer, const LayoutSize& layoutSize) 69 { 70 if (object->style()->imageRendering() == ImageRenderingPixelated 71 && image 72 && (layoutSize.width() > image->width() || layoutSize.height() > image->height() || layoutSize == image->size())) { 73 return InterpolationNone; 74 } 75 76 if (InterpolationDefault == InterpolationLow) 77 return InterpolationLow; 78 79 if (shouldPaintAtLowQuality(context, object, image, layer, layoutSize)) 80 return InterpolationLow; 81 82 // For images that are potentially animated we paint them at medium quality. 83 if (image && image->maybeAnimated()) 84 return InterpolationMedium; 85 86 return InterpolationDefault; 87 } 88 89 ImageQualityController::~ImageQualityController() 90 { 91 // This will catch users of ImageQualityController that forget to call cleanUp. 92 ASSERT(!gImageQualityController || gImageQualityController->isEmpty()); 93 } 94 95 ImageQualityController::ImageQualityController() 96 : m_timer(this, &ImageQualityController::highQualityRepaintTimerFired) 97 , m_animatedResizeIsActive(false) 98 , m_liveResizeOptimizationIsActive(false) 99 { 100 } 101 102 void ImageQualityController::removeLayer(RenderObject* object, LayerSizeMap* innerMap, const void* layer) 103 { 104 if (innerMap) { 105 innerMap->remove(layer); 106 if (innerMap->isEmpty()) 107 objectDestroyed(object); 108 } 109 } 110 111 void ImageQualityController::set(RenderObject* object, LayerSizeMap* innerMap, const void* layer, const LayoutSize& size) 112 { 113 if (innerMap) 114 innerMap->set(layer, size); 115 else { 116 LayerSizeMap newInnerMap; 117 newInnerMap.set(layer, size); 118 m_objectLayerSizeMap.set(object, newInnerMap); 119 } 120 } 121 122 void ImageQualityController::objectDestroyed(RenderObject* object) 123 { 124 m_objectLayerSizeMap.remove(object); 125 if (m_objectLayerSizeMap.isEmpty()) { 126 m_animatedResizeIsActive = false; 127 m_timer.stop(); 128 } 129 } 130 131 void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityController>*) 132 { 133 if (!m_animatedResizeIsActive && !m_liveResizeOptimizationIsActive) 134 return; 135 m_animatedResizeIsActive = false; 136 137 for (ObjectLayerSizeMap::iterator it = m_objectLayerSizeMap.begin(); it != m_objectLayerSizeMap.end(); ++it) { 138 if (LocalFrame* frame = it->key->document().frame()) { 139 // If this renderer's containing FrameView is in live resize, punt the timer and hold back for now. 140 if (frame->view() && frame->view()->inLiveResize()) { 141 restartTimer(); 142 return; 143 } 144 } 145 it->key->setShouldDoFullPaintInvalidation(true); 146 } 147 148 m_liveResizeOptimizationIsActive = false; 149 } 150 151 void ImageQualityController::restartTimer() 152 { 153 m_timer.startOneShot(cLowQualityTimeThreshold, FROM_HERE); 154 } 155 156 bool ImageQualityController::shouldPaintAtLowQuality(GraphicsContext* context, RenderObject* object, Image* image, const void *layer, const LayoutSize& layoutSize) 157 { 158 // If the image is not a bitmap image, then none of this is relevant and we just paint at high 159 // quality. 160 if (!image || !image->isBitmapImage()) 161 return false; 162 163 if (object->style()->imageRendering() == ImageRenderingOptimizeContrast) 164 return true; 165 166 // Look ourselves up in the hashtables. 167 ObjectLayerSizeMap::iterator i = m_objectLayerSizeMap.find(object); 168 LayerSizeMap* innerMap = i != m_objectLayerSizeMap.end() ? &i->value : 0; 169 LayoutSize oldSize; 170 bool isFirstResize = true; 171 if (innerMap) { 172 LayerSizeMap::iterator j = innerMap->find(layer); 173 if (j != innerMap->end()) { 174 isFirstResize = false; 175 oldSize = j->value; 176 } 177 } 178 179 const AffineTransform& currentTransform = context->getCTM(); 180 bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped(); 181 182 // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image 183 // is actually being scaled. 184 LayoutSize scaledImageSize = currentTransform.mapSize(image->size()); 185 LayoutSize scaledLayoutSize = currentTransform.mapSize(roundedIntSize(layoutSize)); 186 187 // If the containing FrameView is being resized, paint at low quality until resizing is finished. 188 if (LocalFrame* frame = object->document().frame()) { 189 bool frameViewIsCurrentlyInLiveResize = frame->view() && frame->view()->inLiveResize(); 190 if (frameViewIsCurrentlyInLiveResize) { 191 set(object, innerMap, layer, scaledLayoutSize); 192 restartTimer(); 193 m_liveResizeOptimizationIsActive = true; 194 return true; 195 } 196 if (m_liveResizeOptimizationIsActive) { 197 // Live resize has ended, paint in HQ and remove this object from the list. 198 removeLayer(object, innerMap, layer); 199 return false; 200 } 201 } 202 203 // See crbug.com/382491. This test is insufficient to ensure that there is no scale 204 // applied in the compositor, but it is probably adequate here. In the worst case we 205 // draw at high quality when we need not. 206 if (!contextIsScaled && scaledLayoutSize == scaledImageSize) { 207 // There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list. 208 removeLayer(object, innerMap, layer); 209 return false; 210 } 211 212 // If an animated resize is active, paint in low quality and kick the timer ahead. 213 if (m_animatedResizeIsActive) { 214 set(object, innerMap, layer, scaledLayoutSize); 215 restartTimer(); 216 return true; 217 } 218 // If this is the first time resizing this image, or its size is the 219 // same as the last resize, draw at high res, but record the paint 220 // size and set the timer. 221 if (isFirstResize || oldSize == scaledLayoutSize) { 222 restartTimer(); 223 set(object, innerMap, layer, scaledLayoutSize); 224 return false; 225 } 226 // If the timer is no longer active, draw at high quality and don't 227 // set the timer. 228 if (!m_timer.isActive()) { 229 removeLayer(object, innerMap, layer); 230 return false; 231 } 232 // This object has been resized to two different sizes while the timer 233 // is active, so draw at low quality, set the flag for animated resizes and 234 // the object to the list for high quality redraw. 235 set(object, innerMap, layer, scaledLayoutSize); 236 m_animatedResizeIsActive = true; 237 restartTimer(); 238 return true; 239 } 240 241 } // namespace blink 242