blob: 6ef4910a0b91a8d110eb98a68fd8016af531771c [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/graphics/paint/GeometryMapper.h"
#include "platform/geometry/LayoutRect.h"
namespace blink {
FloatRect GeometryMapper::mapToVisualRectInDestinationSpace(
const FloatRect& rect,
const PropertyTreeState& sourceState,
const PropertyTreeState& destinationState,
bool& success) {
FloatRect result = localToVisualRectInAncestorSpace(
rect, sourceState, destinationState, success);
if (success)
return result;
return slowMapToVisualRectInDestinationSpace(rect, sourceState,
destinationState, success);
}
FloatRect GeometryMapper::mapRectToDestinationSpace(
const FloatRect& rect,
const PropertyTreeState& sourceState,
const PropertyTreeState& destinationState,
bool& success) {
FloatRect result =
localToAncestorRect(rect, sourceState, destinationState, success);
if (success)
return result;
return slowMapRectToDestinationSpace(rect, sourceState, destinationState,
success);
}
FloatRect GeometryMapper::slowMapToVisualRectInDestinationSpace(
const FloatRect& rect,
const PropertyTreeState& sourceState,
const PropertyTreeState& destinationState,
bool& success) {
const TransformPaintPropertyNode* lcaTransform = leastCommonAncestor(
sourceState.transform(), destinationState.transform());
DCHECK(lcaTransform);
// Assume that the clip of destinationState is an ancestor of the clip of
// sourceState and is under the space of lcaTransform. Otherwise
// localToAncestorClipRect() will fail.
PropertyTreeState lcaState = destinationState;
lcaState.setTransform(lcaTransform);
const auto clipRect = localToAncestorClipRect(sourceState, lcaState, success);
if (!success)
return rect;
FloatRect result = localToAncestorRect(rect, sourceState, lcaState, success);
DCHECK(success);
result.intersect(clipRect);
const TransformationMatrix& destinationToLca =
localToAncestorMatrix(destinationState.transform(), lcaState, success);
DCHECK(success);
if (destinationToLca.isInvertible()) {
success = true;
return destinationToLca.inverse().mapRect(result);
}
success = false;
return rect;
}
FloatRect GeometryMapper::slowMapRectToDestinationSpace(
const FloatRect& rect,
const PropertyTreeState& sourceState,
const PropertyTreeState& destinationState,
bool& success) {
const TransformPaintPropertyNode* lcaTransform = leastCommonAncestor(
sourceState.transform(), destinationState.transform());
DCHECK(lcaTransform);
PropertyTreeState lcaState = sourceState;
lcaState.setTransform(lcaTransform);
FloatRect result = localToAncestorRect(rect, sourceState, lcaState, success);
DCHECK(success);
const TransformationMatrix& destinationToLca =
localToAncestorMatrix(destinationState.transform(), lcaState, success);
DCHECK(success);
if (destinationToLca.isInvertible()) {
success = true;
return destinationToLca.inverse().mapRect(result);
}
success = false;
return rect;
}
FloatRect GeometryMapper::localToVisualRectInAncestorSpace(
const FloatRect& rect,
const PropertyTreeState& localState,
const PropertyTreeState& ancestorState,
bool& success) {
const auto& transformMatrix =
localToAncestorMatrix(localState.transform(), ancestorState, success);
if (!success)
return rect;
FloatRect mappedRect = transformMatrix.mapRect(rect);
const auto clipRect =
localToAncestorClipRect(localState, ancestorState, success);
DCHECK(success);
mappedRect.intersect(clipRect);
return mappedRect;
}
FloatRect GeometryMapper::localToAncestorRect(
const FloatRect& rect,
const PropertyTreeState& localState,
const PropertyTreeState& ancestorState,
bool& success) {
const auto& transformMatrix =
localToAncestorMatrix(localState.transform(), ancestorState, success);
if (!success)
return rect;
return transformMatrix.mapRect(rect);
}
FloatRect GeometryMapper::ancestorToLocalRect(
const FloatRect& rect,
const PropertyTreeState& localState,
const PropertyTreeState& ancestorState,
bool& success) {
const auto& transformMatrix =
localToAncestorMatrix(localState.transform(), ancestorState, success);
if (!success)
return rect;
if (!transformMatrix.isInvertible()) {
success = false;
return rect;
}
success = true;
// TODO(chrishtr): Cache the inverse?
return transformMatrix.inverse().mapRect(rect);
}
PrecomputedDataForAncestor& GeometryMapper::getPrecomputedDataForAncestor(
const PropertyTreeState& ancestorState) {
auto addResult = m_data.add(ancestorState.transform(), nullptr);
if (addResult.isNewEntry)
addResult.storedValue->value = PrecomputedDataForAncestor::create();
return *addResult.storedValue->value;
}
FloatRect GeometryMapper::localToAncestorClipRect(
const PropertyTreeState& localState,
const PropertyTreeState& ancestorState,
bool& success) {
PrecomputedDataForAncestor& precomputedData =
getPrecomputedDataForAncestor(ancestorState);
const ClipPaintPropertyNode* clipNode = localState.clip();
Vector<const ClipPaintPropertyNode*> intermediateNodes;
FloatRect clip(LayoutRect::infiniteIntRect());
bool found = false;
// Iterate over the path from localState.clip to ancestorState.clip. Stop if
// we've found a memoized (precomputed) clip for any particular node.
while (clipNode) {
auto it = precomputedData.toAncestorClipRects.find(clipNode);
if (it != precomputedData.toAncestorClipRects.end()) {
clip = it->value;
found = true;
break;
}
intermediateNodes.append(clipNode);
if (clipNode == ancestorState.clip())
break;
clipNode = clipNode->parent();
}
if (clipNode != ancestorState.clip() && !found) {
success = false;
return clip;
}
// Iterate down from the top intermediate node found in the previous loop,
// computing and memoizing clip rects as we go.
for (auto it = intermediateNodes.rbegin(); it != intermediateNodes.rend();
++it) {
if ((*it) != ancestorState.clip()) {
success = false;
const TransformationMatrix& transformMatrix = localToAncestorMatrix(
(*it)->localTransformSpace(), ancestorState, success);
if (!success)
return clip;
FloatRect mappedRect = transformMatrix.mapRect((*it)->clipRect().rect());
clip.intersect(mappedRect);
}
precomputedData.toAncestorClipRects.set(*it, clip);
}
success = true;
return precomputedData.toAncestorClipRects.find(localState.clip())->value;
}
const TransformationMatrix& GeometryMapper::localToAncestorMatrix(
const TransformPaintPropertyNode* localTransformNode,
const PropertyTreeState& ancestorState,
bool& success) {
PrecomputedDataForAncestor& precomputedData =
getPrecomputedDataForAncestor(ancestorState);
const TransformPaintPropertyNode* transformNode = localTransformNode;
Vector<const TransformPaintPropertyNode*> intermediateNodes;
TransformationMatrix transformMatrix;
bool found = false;
// Iterate over the path from localTransformNode to ancestorState.transform.
// Stop if we've found a memoized (precomputed) transform for any particular
// node.
while (transformNode) {
auto it = precomputedData.toAncestorTransforms.find(transformNode);
if (it != precomputedData.toAncestorTransforms.end()) {
transformMatrix = it->value;
found = true;
break;
}
intermediateNodes.append(transformNode);
if (transformNode == ancestorState.transform())
break;
transformNode = transformNode->parent();
}
if (!found && transformNode != ancestorState.transform()) {
success = false;
return m_identity;
}
// Iterate down from the top intermediate node found in the previous loop,
// computing and memoizing transforms as we go.
for (auto it = intermediateNodes.rbegin(); it != intermediateNodes.rend();
it++) {
if ((*it) != ancestorState.transform()) {
TransformationMatrix localTransformMatrix = (*it)->matrix();
localTransformMatrix.applyTransformOrigin((*it)->origin());
transformMatrix = transformMatrix * localTransformMatrix;
}
precomputedData.toAncestorTransforms.set(*it, transformMatrix);
}
success = true;
return precomputedData.toAncestorTransforms.find(localTransformNode)->value;
}
namespace {
unsigned transformPropertyTreeNodeDepth(
const TransformPaintPropertyNode* node) {
unsigned depth = 0;
while (node) {
depth++;
node = node->parent();
}
return depth;
}
}
const TransformPaintPropertyNode* GeometryMapper::leastCommonAncestor(
const TransformPaintPropertyNode* a,
const TransformPaintPropertyNode* b) {
// Measure both depths.
unsigned depthA = transformPropertyTreeNodeDepth(a);
unsigned depthB = transformPropertyTreeNodeDepth(b);
// Make it so depthA >= depthB.
if (depthA < depthB) {
std::swap(a, b);
std::swap(depthA, depthB);
}
// Make it so depthA == depthB.
while (depthA > depthB) {
a = a->parent();
depthA--;
}
// Walk up until we find the ancestor.
while (a != b) {
a = a->parent();
b = b->parent();
}
return a;
}
} // namespace blink