blob: f3707fde1913e4513fd3cfe74fb8be87a4df8cb5 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gfx/geometry/transform_util.h"
#include <algorithm>
#include <cmath>
#include <ostream>
#include <string>
#include "base/check.h"
#include "ui/gfx/geometry/point3_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
namespace gfx {
namespace {
template <int n>
void Combine(double* out,
const double* a,
const double* b,
double scale_a,
double scale_b) {
for (int i = 0; i < n; ++i)
out[i] = a[i] * scale_a + b[i] * scale_b;
}
} // namespace
Transform GetScaleTransform(const Point& anchor, float scale) {
Transform transform;
transform.Translate(anchor.x() * (1 - scale), anchor.y() * (1 - scale));
transform.Scale(scale, scale);
return transform;
}
DecomposedTransform BlendDecomposedTransforms(const DecomposedTransform& to,
const DecomposedTransform& from,
double progress) {
DecomposedTransform out;
double scalea = progress;
double scaleb = 1.0 - progress;
Combine<3>(out.translate, to.translate, from.translate, scalea, scaleb);
Combine<3>(out.scale, to.scale, from.scale, scalea, scaleb);
Combine<3>(out.skew, to.skew, from.skew, scalea, scaleb);
Combine<4>(out.perspective, to.perspective, from.perspective, scalea, scaleb);
out.quaternion = from.quaternion.Slerp(to.quaternion, progress);
return out;
}
DecomposedTransform AccumulateDecomposedTransforms(
const DecomposedTransform& a,
const DecomposedTransform& b) {
DecomposedTransform out;
// Translate is a simple addition.
for (size_t i = 0; i < std::size(a.translate); i++)
out.translate[i] = a.translate[i] + b.translate[i];
// Scale is accumulated using 1-based addition.
for (size_t i = 0; i < std::size(a.scale); i++)
out.scale[i] = a.scale[i] + b.scale[i] - 1;
// Skew can be added.
for (size_t i = 0; i < std::size(a.skew); i++)
out.skew[i] = a.skew[i] + b.skew[i];
// We sum the perspective components; note that w is 1-based.
for (size_t i = 0; i < std::size(a.perspective); i++)
out.perspective[i] = a.perspective[i] + b.perspective[i];
out.perspective[3] -= 1;
// To accumulate quaternions, we multiply them. This is equivalent to 'adding'
// the rotations that they represent.
out.quaternion = a.quaternion * b.quaternion;
return out;
}
Transform TransformAboutPivot(const PointF& pivot, const Transform& transform) {
Transform result;
result.Translate(pivot.x(), pivot.y());
result.PreConcat(transform);
result.Translate(-pivot.x(), -pivot.y());
return result;
}
Transform TransformBetweenRects(const RectF& src, const RectF& dst) {
DCHECK(!src.IsEmpty());
Transform result;
result.Translate(dst.origin() - src.origin());
result.Scale(dst.width() / src.width(), dst.height() / src.height());
return result;
}
AxisTransform2d OrthoProjectionTransform(float left,
float right,
float bottom,
float top) {
// Use the standard formula to map the clipping frustum to the square from
// [-1, -1] to [1, 1].
float delta_x = right - left;
float delta_y = top - bottom;
if (!delta_x || !delta_y)
return AxisTransform2d();
return AxisTransform2d::FromScaleAndTranslation(
Vector2dF(2.0f / delta_x, 2.0f / delta_y),
Vector2dF(-(right + left) / delta_x, -(top + bottom) / delta_y));
}
AxisTransform2d WindowTransform(int x, int y, int width, int height) {
// Map from ([-1, -1] to [1, 1]) -> ([x, y] to [x + width, y + height]).
return AxisTransform2d::FromScaleAndTranslation(
Vector2dF(width * 0.5f, height * 0.5f),
Vector2dF(x + width * 0.5f, y + height * 0.5f));
}
static inline bool NearlyZero(double value) {
return std::abs(value) < std::numeric_limits<double>::epsilon();
}
static inline float ScaleOnAxis(double a, double b, double c) {
if (NearlyZero(b) && NearlyZero(c))
return std::abs(a);
if (NearlyZero(a) && NearlyZero(c))
return std::abs(b);
if (NearlyZero(a) && NearlyZero(b))
return std::abs(c);
// Do the sqrt as a double to not lose precision.
return static_cast<float>(std::sqrt(a * a + b * b + c * c));
}
absl::optional<Vector2dF> TryComputeTransform2dScaleComponents(
const Transform& transform) {
if (transform.rc(3, 0) != 0.0f || transform.rc(3, 1) != 0.0f) {
return absl::nullopt;
}
float w = transform.rc(3, 3);
if (!std::isnormal(w)) {
return absl::nullopt;
}
float w_scale = 1.0f / w;
// In theory, this shouldn't be using the matrix.getDouble(2, 0) and
// .getDouble(1, 0) values; creating a large transfer from input x or
// y (in the layer) to output z has no visible difference when the
// transform being considered is a transform to device space, since
// the resulting z values are ignored. However, ignoring them here
// might be risky because it would mean that we would have more
// variation in the results under animation of rotateX() or rotateY(),
// and we'd be relying more heavily on code to compute correct scales
// during animation. Currently some such code only considers the
// endpoints, which would become problematic for cases like animation
// from rotateY(-60deg) to rotateY(60deg).
float x_scale =
ScaleOnAxis(transform.rc(0, 0), transform.rc(1, 0), transform.rc(2, 0));
float y_scale =
ScaleOnAxis(transform.rc(0, 1), transform.rc(1, 1), transform.rc(2, 1));
return Vector2dF(x_scale * w_scale, y_scale * w_scale);
}
Vector2dF ComputeTransform2dScaleComponents(const Transform& transform,
float fallback_value) {
absl::optional<Vector2dF> scale =
TryComputeTransform2dScaleComponents(transform);
if (scale) {
return *scale;
}
return Vector2dF(fallback_value, fallback_value);
}
float ComputeApproximateMaxScale(const Transform& transform) {
gfx::RectF unit = transform.MapRect(RectF(0.f, 0.f, 1.f, 1.f));
return std::max(unit.width(), unit.height());
}
} // namespace gfx