200 lines
7.6 KiB
C++
200 lines
7.6 KiB
C++
/*
|
|
* Copyright (C) 2013 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "ImageQualityController.h"
|
|
|
|
#include "Frame.h"
|
|
#include "GraphicsContext.h"
|
|
#include "Page.h"
|
|
#include "RenderBoxModelObject.h"
|
|
#include "RenderView.h"
|
|
|
|
namespace WebCore {
|
|
|
|
static const double cInterpolationCutoff = 800. * 800.;
|
|
static const Seconds lowQualityTimeThreshold { 500_ms };
|
|
|
|
ImageQualityController::ImageQualityController(const RenderView& renderView)
|
|
: m_renderView(renderView)
|
|
, m_timer(*this, &ImageQualityController::highQualityRepaintTimerFired)
|
|
{
|
|
}
|
|
|
|
void ImageQualityController::removeLayer(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer)
|
|
{
|
|
if (!innerMap)
|
|
return;
|
|
innerMap->remove(layer);
|
|
if (innerMap->isEmpty())
|
|
removeObject(object);
|
|
}
|
|
|
|
void ImageQualityController::set(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer, const LayoutSize& size)
|
|
{
|
|
if (innerMap)
|
|
innerMap->set(layer, size);
|
|
else {
|
|
LayerSizeMap newInnerMap;
|
|
newInnerMap.set(layer, size);
|
|
m_objectLayerSizeMap.set(object, newInnerMap);
|
|
}
|
|
}
|
|
|
|
void ImageQualityController::removeObject(RenderBoxModelObject* object)
|
|
{
|
|
m_objectLayerSizeMap.remove(object);
|
|
if (m_objectLayerSizeMap.isEmpty()) {
|
|
m_animatedResizeIsActive = false;
|
|
m_timer.stop();
|
|
}
|
|
}
|
|
|
|
void ImageQualityController::highQualityRepaintTimerFired()
|
|
{
|
|
if (m_renderView.renderTreeBeingDestroyed())
|
|
return;
|
|
if (!m_animatedResizeIsActive && !m_liveResizeOptimizationIsActive)
|
|
return;
|
|
m_animatedResizeIsActive = false;
|
|
|
|
// If the FrameView is in live resize, punt the timer and hold back for now.
|
|
if (m_renderView.frameView().inLiveResize()) {
|
|
restartTimer();
|
|
return;
|
|
}
|
|
|
|
for (auto it = m_objectLayerSizeMap.begin(), end = m_objectLayerSizeMap.end(); it != end; ++it)
|
|
it->key->repaint();
|
|
|
|
m_liveResizeOptimizationIsActive = false;
|
|
}
|
|
|
|
void ImageQualityController::restartTimer()
|
|
{
|
|
m_timer.startOneShot(lowQualityTimeThreshold);
|
|
}
|
|
|
|
std::optional<InterpolationQuality> ImageQualityController::interpolationQualityFromStyle(const RenderStyle& style)
|
|
{
|
|
switch (style.imageRendering()) {
|
|
case ImageRendering::OptimizeSpeed:
|
|
return InterpolationQuality::Low;
|
|
case ImageRendering::CrispEdges:
|
|
case ImageRendering::Pixelated:
|
|
return InterpolationQuality::DoNotInterpolate;
|
|
case ImageRendering::OptimizeQuality:
|
|
return InterpolationQuality::Default; // FIXME: CSS 3 Images says that optimizeQuality should behave like 'auto', but that prevents authors from overriding this low quality rendering behavior.
|
|
case ImageRendering::Auto:
|
|
break;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
InterpolationQuality ImageQualityController::chooseInterpolationQuality(GraphicsContext& context, RenderBoxModelObject* object, Image& image, const void* layer, const LayoutSize& size)
|
|
{
|
|
// If the image is not a bitmap image, then none of this is relevant and we just paint at high quality.
|
|
if (!(image.isBitmapImage() || image.isPDFDocumentImage()) || context.paintingDisabled())
|
|
return InterpolationQuality::Default;
|
|
|
|
if (std::optional<InterpolationQuality> styleInterpolation = interpolationQualityFromStyle(object->style()))
|
|
return styleInterpolation.value();
|
|
|
|
// Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image
|
|
// is actually being scaled.
|
|
IntSize imageSize(image.width(), image.height());
|
|
|
|
// Look ourselves up in the hashtables.
|
|
auto i = m_objectLayerSizeMap.find(object);
|
|
LayerSizeMap* innerMap = i != m_objectLayerSizeMap.end() ? &i->value : 0;
|
|
LayoutSize oldSize;
|
|
bool isFirstResize = true;
|
|
if (innerMap) {
|
|
LayerSizeMap::iterator j = innerMap->find(layer);
|
|
if (j != innerMap->end()) {
|
|
isFirstResize = false;
|
|
oldSize = j->value;
|
|
}
|
|
}
|
|
|
|
// If the containing FrameView is being resized, paint at low quality until resizing is finished.
|
|
if (Frame* frame = object->document().frame()) {
|
|
bool frameViewIsCurrentlyInLiveResize = frame->view() && frame->view()->inLiveResize();
|
|
if (frameViewIsCurrentlyInLiveResize) {
|
|
set(object, innerMap, layer, size);
|
|
restartTimer();
|
|
m_liveResizeOptimizationIsActive = true;
|
|
return InterpolationQuality::Low;
|
|
}
|
|
if (m_liveResizeOptimizationIsActive)
|
|
return InterpolationQuality::Default;
|
|
}
|
|
|
|
const AffineTransform& currentTransform = context.getCTM();
|
|
bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped();
|
|
if (!contextIsScaled && size == imageSize) {
|
|
// There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list.
|
|
removeLayer(object, innerMap, layer);
|
|
return InterpolationQuality::Default;
|
|
}
|
|
|
|
// There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case.
|
|
if (m_renderView.page().inLowQualityImageInterpolationMode()) {
|
|
double totalPixels = static_cast<double>(image.width()) * static_cast<double>(image.height());
|
|
if (totalPixels > cInterpolationCutoff)
|
|
return InterpolationQuality::Low;
|
|
}
|
|
|
|
// If an animated resize is active, paint in low quality and kick the timer ahead.
|
|
if (m_animatedResizeIsActive) {
|
|
set(object, innerMap, layer, size);
|
|
restartTimer();
|
|
return InterpolationQuality::Low;
|
|
}
|
|
// If this is the first time resizing this image, or its size is the
|
|
// same as the last resize, draw at high res, but record the paint
|
|
// size and set the timer.
|
|
if (isFirstResize || oldSize == size) {
|
|
restartTimer();
|
|
set(object, innerMap, layer, size);
|
|
return InterpolationQuality::Default;
|
|
}
|
|
// If the timer is no longer active, draw at high quality and don't
|
|
// set the timer.
|
|
if (!m_timer.isActive()) {
|
|
removeLayer(object, innerMap, layer);
|
|
return InterpolationQuality::Default;
|
|
}
|
|
// This object has been resized to two different sizes while the timer
|
|
// is active, so draw at low quality, set the flag for animated resizes and
|
|
// the object to the list for high quality redraw.
|
|
set(object, innerMap, layer, size);
|
|
m_animatedResizeIsActive = true;
|
|
restartTimer();
|
|
return InterpolationQuality::Low;
|
|
}
|
|
|
|
}
|