blob: 99a043ba14d30ca8d492a2e926f2f1b9c1473a09 [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2020, Raspberry Pi (Trading) Limited
*
* transform.cpp - 2D plane transforms.
*/
#include <libcamera/transform.h>
/**
* \file transform.h
* \brief Enum to represent and manipulate 2D plane transforms
*/
namespace libcamera {
/**
* \enum Transform
* \brief Enum to represent a 2D plane transform
*
* The Transform can take 8 distinct values, representing the usual 2D plane
* transforms listed below. Each of these transforms can be constructed
* out of 3 basic operations, namely a horizontal flip (mirror), a vertical
* flip, and a transposition (about the main diagonal). The transforms are
* encoded such that a single bit indicates the presence of each of the 3
* basic operations:
*
* - bit 0 - presence of a horizontal flip
* - bit 1 - presence of a vertical flip
* - bit 2 - presence of a transposition.
*
* We regard these 3 basic operations as being applied in a specific order:
* first the two flip operations (actually they commute, so the order between
* them is unimportant) and finally any transpose operation.
*
* Functions are provided to manipulate directly the bits within the transform
* encoding, but there are also higher-level functions to invert and compose
* transforms. Transforms are composed according to the usual mathematical
* convention such that the right transform is applied first, and the left
* transform is applied second.
*
* Finally, we have a total of 8 distinct transformations, as follows (a
* couple of them have additional synonyms for convenience). We illustrate each
* with its nominal effect on a rectangle with vertices labelled A, B, C and D.
*
* \sa https://en.wikipedia.org/wiki/Examples_of_groups#dihedral_group_of_order_8
*
* The set of 2D plane transforms is also known as the symmetry group of a
* square, described in the link. Note that the group can be generated by
* only 2 elements (the horizontal flip and a 90 degree rotation, for
* example), however, the encoding used here makes the presence of the vertical
* flip explicit.
*
* \var Transform::Identity
*
* Identity transform.
~~~
A-B A-B
Input image | | goes to output image | |
C-D C-D
~~~
* Numeric value: 0 (no bits set).
*
* \var Transform::Rot0
*
* Synonym for Transform::Identity (zero degree rotation).
*
* \var Transform::HFlip
*
* Horizontal flip.
~~~
A-B B-A
Input image | | goes to output image | |
C-D D-C
~~~
* Numeric value: 1 (horizontal flip bit set only).
*
* \var Transform::VFlip
*
* Vertical flip.
~~~
A-B C-D
Input image | | goes to output image | |
C-D A-B
~~~
* Numeric value: 2 (vertical flip bit set only).
*
* \var Transform::HVFlip
*
* Horizontal and vertical flip (identical to a 180 degree rotation).
~~~
A-B D-C
Input image | | goes to output image | |
C-D B-A
~~~
* Numeric value: 3 (horizontal and vertical flip bits set).
*
* \var Transform::Rot180
*
* Synonym for `HVFlip` (180 degree rotation).
*
* \var Transform::Transpose
*
* Transpose (about the main diagonal).
~~~
A-B A-C
Input image | | goes to output image | |
C-D B-D
~~~
* Numeric value: 4 (transpose bit set only).
*
* \var Transform::Rot270
*
* Rotation by 270 degrees clockwise (90 degrees anticlockwise).
~~~
A-B B-D
Input image | | goes to output image | |
C-D A-C
~~~
* Numeric value: 5 (transpose and horizontal flip bits set).
*
* \var Transform::Rot90
*
* Rotation by 90 degrees clockwise (270 degrees anticlockwise).
~~~
A-B C-A
Input image | | goes to output image | |
C-D D-B
~~~
* Numeric value: 6 (transpose and vertical flip bits set).
*
* \var Transform::Rot180Transpose
*
* Rotation by 180 degrees followed by transpose (alternatively, transposition
* about the "opposite diagonal").
~~~
A-B D-B
Input image | | goes to output image | |
C-D C-A
~~~
* Numeric value: 7 (all bits set).
*/
/**
* \fn operator &(Transform t0, Transform t1)
* \brief Apply bitwise AND operator between the bits in the two transforms
* \param[in] t0 The first transform
* \param[in] t1 The second transform
*/
/**
* \fn operator |(Transform t0, Transform t1)
* \brief Apply bitwise OR operator between the bits in the two transforms
* \param[in] t0 The first transform
* \param[in] t1 The second transform
*/
/**
* \fn operator ^(Transform t0, Transform t1)
* \brief Apply bitwise XOR operator between the bits in the two transforms
* \param[in] t0 The first transform
* \param[in] t1 The second transform
*/
/**
* \fn operator &=(Transform &t0, Transform t1)
* \brief Apply bitwise AND-assignment operator between the bits in the two
* transforms
* \param[in] t0 The first transform
* \param[in] t1 The second transform
*/
/**
* \fn operator |=(Transform &t0, Transform t1)
* \brief Apply bitwise OR-assignment operator between the bits in the two
* transforms
* \param[in] t0 The first transform
* \param[in] t1 The second transform
*/
/**
* \fn operator ^=(Transform &t0, Transform t1)
* \brief Apply bitwise XOR-assignment operator between the bits in the two
* transforms
* \param[in] t0 The first transform
* \param[in] t1 The second transform
*/
/**
* \brief Compose two transforms together
* \param[in] t1 The second transform
* \param[in] t0 The first transform
*
* Composing transforms follows the usual mathematical convention for
* composing functions. That is, when performing `t1 * t0`, \a t0 is applied
* first, and then \a t1.
* For example, `Transpose * HFlip` performs `HFlip` first and then the
* `Transpose` yielding `Rot270`, as shown below.
~~~
A-B B-A B-D
Input image | | -> HFLip -> | | -> Transpose -> | | = Rot270
C-D D-C A-C
~~~
* Note that composition is generally non-commutative for Transforms,
* and not the same as XOR-ing the underlying bit representations.
*/
Transform operator*(Transform t1, Transform t0)
{
/*
* Reorder the operations so that we imagine doing t0's transpose
* (if any) after t1's flips. The effect is to swap t1's hflips for
* vflips and vice versa, after which we can just xor all the bits.
*/
Transform reordered = t1;
if (!!(t0 & Transform::Transpose)) {
reordered = t1 & Transform::Transpose;
if (!!(t1 & Transform::HFlip))
reordered |= Transform::VFlip;
if (!!(t1 & Transform::VFlip))
reordered |= Transform::HFlip;
}
return reordered ^ t0;
}
/**
* \brief Invert a transform
* \param[in] t The transform to be inverted
*
* That is, we return the transform such that `t * (-t)` and `(-t) * t` both
* yield the identity transform.
*/
Transform operator-(Transform t)
{
/* All are self-inverses, except for Rot270 and Rot90. */
static const Transform inverses[] = {
Transform::Identity,
Transform::HFlip,
Transform::VFlip,
Transform::HVFlip,
Transform::Transpose,
Transform::Rot90,
Transform::Rot270,
Transform::Rot180Transpose
};
return inverses[static_cast<int>(t)];
}
/**
* \fn operator!(Transform t)
* \brief Return `true` if the transform is the `Identity`, otherwise `false`
* \param[in] t The transform to be tested
*/
/**
* \fn operator~(Transform t)
* \brief Return the transform with all the bits inverted individually
* \param[in] t The transform of which the bits will be inverted
*
* This inverts the bits that encode the transform in a bitwise manner. Note
* that this is not the proper inverse of transform \a t (for which use \a
* operator-).
*/
/**
* \brief Return the transform representing a rotation of the given angle
* clockwise
* \param[in] angle The angle of rotation in a clockwise sense. Negative values
* can be used to represent anticlockwise rotations
* \param[out] success Set to `true` if the angle is a multiple of 90 degrees,
* otherwise `false`
* \return The transform corresponding to the rotation if \a success was set to
* `true`, otherwise the `Identity` transform
*/
Transform transformFromRotation(int angle, bool *success)
{
angle = angle % 360;
if (angle < 0)
angle += 360;
if (success != nullptr)
*success = true;
switch (angle) {
case 0:
return Transform::Identity;
case 90:
return Transform::Rot90;
case 180:
return Transform::Rot180;
case 270:
return Transform::Rot270;
}
if (success != nullptr)
*success = false;
return Transform::Identity;
}
/**
* \brief Return a character string describing the transform
* \param[in] t The transform to be described.
*/
const char *transformToString(Transform t)
{
static const char *strings[] = {
"identity",
"hflip",
"vflip",
"hvflip",
"transpose",
"rot270",
"rot90",
"rot180transpose"
};
return strings[static_cast<int>(t)];
}
} /* namespace libcamera */