[libcamera-devel,v6,0/12] libcamera: Replace CameraConfiguration::transform
mbox series

Message ID 20231019140133.32090-1-jacopo.mondi@ideasonboard.com
Headers show
Series
  • libcamera: Replace CameraConfiguration::transform
Related show

Message

Jacopo Mondi Oct. 19, 2023, 2:01 p.m. UTC
v5->v6:
- Rename enum members
- replace images with svg
- fix documentation

v4->v5:
- Add support in Python layer
- Rebased on master

v3->v4:
- Remove unrelated change in 8/10
- Remove a double test case
- Add David's tags

v2->v3:
- Move Orientation out of CameraConfiguration to a dedicated file
- Change operator*(Transform, Transform)
- Address David's comments in documentation and commit messages

v1->v2:
- Provide a better definitio of Orientation based on two basic operations
- Provide functions to combine Orientation and Transpose
- Provide tests
- Do not adjust properties::Rotation anymore

This series proposes to replace the usage to Transform in the public API in
favour of a new Orientation type, defined based on the EXIF
specification, tag 274, 'orientation'. For reference this is the same as
implemented by gstreamer:
https://gstreamer.freedesktop.org/documentation/gstreamer/gsttaglist.html?gi-language=c#GST_TAG_IMAGE_ORIENTATION

(the names of the enum values are different as gstreamer expresses the
"correction" while we express the actual orientation in memory buffers).

The newly introduced CameraConfiguration::orientation replaces the
existing CameraConfiguration::tranform, and it is meant for application to
express how they would like the images to be oriented, not to tell libcamera
what to do. As an example, passing in 'rotation0' means that the application
expects the images to be rotated upright, and doesn't tell libcamera not to
apply any rotation like passing in "Transform::Identity" did.

The value CameraConfiguration::orientation is set to after a validate() also
differs in meaning, as instead of reporting "what applications have to do
to obtain what they originally asked for" it simply reports the actual
orientation of the stream: this means that if libcamera cannot fully satisfy the
user request it will set ::orientation to report the native images rotation
and the CameraConfiguration::status will be set to Adjusted.

Handling of 90 and 270 degrees rotation has also changed: as the camera sensor
cannot correct rotations that include a transposition, requests for a 90/270
corrections are ignored. This makes it clear and less confusing for applications
that they have to deal with correction fully by themselves. As an example, with
the current implementation if the application requires a Rot270 (HFlip +
Transpose) libcamera will do the HFlip and leave transposition to the upper
layers. There is no clear advantage in doing so, and display compositors are
better suited for handling transpositions and flipping in a single pass instead
of having the library try to handle part of that.

This series clearly breaks the application API as it removes a member from
CameraConfiguration, so it should be introduced probably only when a new release
is cut.

Tested on RPi imx219 with the newly introduced cam option, flip, mirror and
rotation work as expected.

*** BLURB HERE ***

Jacopo Mondi (12):
  libcamera: camera_sensor: Cache rotationTransform_
  libcamera: camera: Introduce Orientation
  Documentation: Add figures to document Orientation
  libcamera: properties: Make 'Rotation' the mounting rotation
  libcamera: transform: Add functions to convert Orientation
  libcamera: transform: Invert operator*() operands
  libcamera: transform: Add operations with Orientation
  test: Add unit test for Transform and Orientation
  py: libcamera: Define and use Orientation
  libcamera: Use CameraConfiguration::orientation
  apps: cam: Add option to set stream orientation
  py: cam: Add option to set stream orientation

 Documentation/Doxyfile.in                     |   2 +
 Documentation/rotation/rotate0.svg            | 132 +++++++
 Documentation/rotation/rotate0Mirror.svg      | 135 +++++++
 Documentation/rotation/rotate180.svg          | 135 +++++++
 Documentation/rotation/rotate180Mirror.svg    | 135 +++++++
 Documentation/rotation/rotate270.svg          | 135 +++++++
 Documentation/rotation/rotate270Mirror.svg    | 135 +++++++
 Documentation/rotation/rotate90.svg           | 135 +++++++
 Documentation/rotation/rotate90Mirror.svg     | 135 +++++++
 include/libcamera/camera.h                    |   4 +-
 include/libcamera/internal/camera_sensor.h    |   5 +-
 include/libcamera/meson.build                 |   1 +
 include/libcamera/orientation.h               |  28 ++
 include/libcamera/transform.h                 |   7 +
 src/apps/cam/camera_session.cpp               |  18 +
 src/apps/cam/main.cpp                         |   5 +
 src/apps/cam/main.h                           |   1 +
 src/libcamera/camera.cpp                      |  20 +-
 src/libcamera/camera_sensor.cpp               | 131 ++++---
 src/libcamera/meson.build                     |   1 +
 src/libcamera/orientation.cpp                 |  79 +++++
 src/libcamera/pipeline/ipu3/ipu3.cpp          |   6 +-
 src/libcamera/pipeline/rkisp1/rkisp1.cpp      |   8 +-
 .../pipeline/rpi/common/pipeline_base.cpp     |   9 +-
 src/libcamera/pipeline/simple/simple.cpp      |   6 +-
 src/libcamera/pipeline/uvcvideo/uvcvideo.cpp  |   4 +-
 src/libcamera/pipeline/vimc/vimc.cpp          |   4 +-
 src/libcamera/property_ids.yaml               |   8 +-
 src/libcamera/transform.cpp                   | 112 +++++-
 src/py/cam/cam.py                             |  21 ++
 src/py/libcamera/py_enums.cpp                 |  10 +
 src/py/libcamera/py_main.cpp                  |   2 +-
 test/meson.build                              |   1 +
 test/transform.cpp                            | 329 ++++++++++++++++++
 34 files changed, 1787 insertions(+), 112 deletions(-)
 create mode 100644 Documentation/rotation/rotate0.svg
 create mode 100644 Documentation/rotation/rotate0Mirror.svg
 create mode 100644 Documentation/rotation/rotate180.svg
 create mode 100644 Documentation/rotation/rotate180Mirror.svg
 create mode 100644 Documentation/rotation/rotate270.svg
 create mode 100644 Documentation/rotation/rotate270Mirror.svg
 create mode 100644 Documentation/rotation/rotate90.svg
 create mode 100644 Documentation/rotation/rotate90Mirror.svg
 create mode 100644 include/libcamera/orientation.h
 create mode 100644 src/libcamera/orientation.cpp
 create mode 100644 test/transform.cpp

--
2.42.0

Comments

Laurent Pinchart Oct. 22, 2023, 10:56 p.m. UTC | #1
Hi Jacopo,

Thank you for the patches.

CC'ing David, for feedback on the impact on Raspberry Pi (if any).

On Thu, Oct 19, 2023 at 04:01:21PM +0200, Jacopo Mondi via libcamera-devel wrote:
> v5->v6:
> - Rename enum members
> - replace images with svg
> - fix documentation
> 
> v4->v5:
> - Add support in Python layer
> - Rebased on master
> 
> v3->v4:
> - Remove unrelated change in 8/10
> - Remove a double test case
> - Add David's tags
> 
> v2->v3:
> - Move Orientation out of CameraConfiguration to a dedicated file
> - Change operator*(Transform, Transform)
> - Address David's comments in documentation and commit messages
> 
> v1->v2:
> - Provide a better definitio of Orientation based on two basic operations
> - Provide functions to combine Orientation and Transpose
> - Provide tests
> - Do not adjust properties::Rotation anymore
> 
> This series proposes to replace the usage to Transform in the public API in
> favour of a new Orientation type, defined based on the EXIF
> specification, tag 274, 'orientation'. For reference this is the same as
> implemented by gstreamer:
> https://gstreamer.freedesktop.org/documentation/gstreamer/gsttaglist.html?gi-language=c#GST_TAG_IMAGE_ORIENTATION
> 
> (the names of the enum values are different as gstreamer expresses the
> "correction" while we express the actual orientation in memory buffers).
> 
> The newly introduced CameraConfiguration::orientation replaces the
> existing CameraConfiguration::tranform, and it is meant for application to
> express how they would like the images to be oriented, not to tell libcamera
> what to do. As an example, passing in 'rotation0' means that the application
> expects the images to be rotated upright, and doesn't tell libcamera not to
> apply any rotation like passing in "Transform::Identity" did.
> 
> The value CameraConfiguration::orientation is set to after a validate() also
> differs in meaning, as instead of reporting "what applications have to do
> to obtain what they originally asked for" it simply reports the actual
> orientation of the stream: this means that if libcamera cannot fully satisfy the
> user request it will set ::orientation to report the native images rotation
> and the CameraConfiguration::status will be set to Adjusted.
> 
> Handling of 90 and 270 degrees rotation has also changed: as the camera sensor
> cannot correct rotations that include a transposition, requests for a 90/270
> corrections are ignored. This makes it clear and less confusing for applications
> that they have to deal with correction fully by themselves. As an example, with
> the current implementation if the application requires a Rot270 (HFlip +
> Transpose) libcamera will do the HFlip and leave transposition to the upper
> layers. There is no clear advantage in doing so, and display compositors are
> better suited for handling transpositions and flipping in a single pass instead
> of having the library try to handle part of that.
> 
> This series clearly breaks the application API as it removes a member from
> CameraConfiguration, so it should be introduced probably only when a new release
> is cut.
> 
> Tested on RPi imx219 with the newly introduced cam option, flip, mirror and
> rotation work as expected.

Overall this looks good to me. I've just sent a series of patches on top
of thes series (numbered 13/12 to 21/12), with the first patches being
small fixups that I propose squashing into the corresponding base patch.
The last three patches would require more complex rework of your series
if we wanted to squash them, so I think we can keep them on top.

I wanted to replace the

Orientation operator*(const Orientation &o, const Transform &t);

with a

Orientation Transform::operator()(const Orientation &o);

in order to be able to write

	Transform t = ...;
	Orientation o1 = ...,
	Orientation o2 = t(o1);

or also

	Orientation o1 = ...,
	Orientation o2 = Transform::HFlip(o1);

which I think would be a nicer syntax. However, I couldn't find a way to
nicely do so :-( Transform is currently an enum class, which is not a
class, and thus can't have member operators. The call operator having no
non-member (outside of the class definition) variant, I had to try and
turn Transform into a class. I got stuck at the point where HFlip had to
be a static constexpr member of the Transform class (to be able to write
`Transform::HFlip`), but also an instance of the Transform class (to be
able to write `Transform::HFlip(o)`). The compiler rightfully complained
that I was trying to use a type that hasn't been fully defined yet. I
gave up for now (but would be very interested into hearing from a C++
enthousiast on how something similar could be achieved). If we keep the
multiplication operator as-is, then your patch 06/12 that inverts the
order of the operands to the multiplication operator between two
Transform instances is needed.

If you're fine with the additional changes, I can handle the squashing
and pushing. I'll need R-b tags for the last three patches though. As
for this series, with the changes on top,

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

An additional change I would like to see on top would be the rewrite of
the Python Transform class in Python, without going through C++
bindings. This would allow us to drop the transformFromRotation() which
is used in the Python bindings code only. Any opinion on this ?

> Jacopo Mondi (12):
>   libcamera: camera_sensor: Cache rotationTransform_
>   libcamera: camera: Introduce Orientation
>   Documentation: Add figures to document Orientation
>   libcamera: properties: Make 'Rotation' the mounting rotation
>   libcamera: transform: Add functions to convert Orientation
>   libcamera: transform: Invert operator*() operands
>   libcamera: transform: Add operations with Orientation
>   test: Add unit test for Transform and Orientation
>   py: libcamera: Define and use Orientation
>   libcamera: Use CameraConfiguration::orientation
>   apps: cam: Add option to set stream orientation
>   py: cam: Add option to set stream orientation
> 
>  Documentation/Doxyfile.in                     |   2 +
>  Documentation/rotation/rotate0.svg            | 132 +++++++
>  Documentation/rotation/rotate0Mirror.svg      | 135 +++++++
>  Documentation/rotation/rotate180.svg          | 135 +++++++
>  Documentation/rotation/rotate180Mirror.svg    | 135 +++++++
>  Documentation/rotation/rotate270.svg          | 135 +++++++
>  Documentation/rotation/rotate270Mirror.svg    | 135 +++++++
>  Documentation/rotation/rotate90.svg           | 135 +++++++
>  Documentation/rotation/rotate90Mirror.svg     | 135 +++++++
>  include/libcamera/camera.h                    |   4 +-
>  include/libcamera/internal/camera_sensor.h    |   5 +-
>  include/libcamera/meson.build                 |   1 +
>  include/libcamera/orientation.h               |  28 ++
>  include/libcamera/transform.h                 |   7 +
>  src/apps/cam/camera_session.cpp               |  18 +
>  src/apps/cam/main.cpp                         |   5 +
>  src/apps/cam/main.h                           |   1 +
>  src/libcamera/camera.cpp                      |  20 +-
>  src/libcamera/camera_sensor.cpp               | 131 ++++---
>  src/libcamera/meson.build                     |   1 +
>  src/libcamera/orientation.cpp                 |  79 +++++
>  src/libcamera/pipeline/ipu3/ipu3.cpp          |   6 +-
>  src/libcamera/pipeline/rkisp1/rkisp1.cpp      |   8 +-
>  .../pipeline/rpi/common/pipeline_base.cpp     |   9 +-
>  src/libcamera/pipeline/simple/simple.cpp      |   6 +-
>  src/libcamera/pipeline/uvcvideo/uvcvideo.cpp  |   4 +-
>  src/libcamera/pipeline/vimc/vimc.cpp          |   4 +-
>  src/libcamera/property_ids.yaml               |   8 +-
>  src/libcamera/transform.cpp                   | 112 +++++-
>  src/py/cam/cam.py                             |  21 ++
>  src/py/libcamera/py_enums.cpp                 |  10 +
>  src/py/libcamera/py_main.cpp                  |   2 +-
>  test/meson.build                              |   1 +
>  test/transform.cpp                            | 329 ++++++++++++++++++
>  34 files changed, 1787 insertions(+), 112 deletions(-)
>  create mode 100644 Documentation/rotation/rotate0.svg
>  create mode 100644 Documentation/rotation/rotate0Mirror.svg
>  create mode 100644 Documentation/rotation/rotate180.svg
>  create mode 100644 Documentation/rotation/rotate180Mirror.svg
>  create mode 100644 Documentation/rotation/rotate270.svg
>  create mode 100644 Documentation/rotation/rotate270Mirror.svg
>  create mode 100644 Documentation/rotation/rotate90.svg
>  create mode 100644 Documentation/rotation/rotate90Mirror.svg
>  create mode 100644 include/libcamera/orientation.h
>  create mode 100644 src/libcamera/orientation.cpp
>  create mode 100644 test/transform.cpp
Jacopo Mondi Oct. 23, 2023, 8:58 a.m. UTC | #2
On Mon, Oct 23, 2023 at 01:56:30AM +0300, Laurent Pinchart via libcamera-devel wrote:
> Hi Jacopo,
>
> Thank you for the patches.
>
> CC'ing David, for feedback on the impact on Raspberry Pi (if any).
>
> On Thu, Oct 19, 2023 at 04:01:21PM +0200, Jacopo Mondi via libcamera-devel wrote:
> > v5->v6:
> > - Rename enum members
> > - replace images with svg
> > - fix documentation
> >
> > v4->v5:
> > - Add support in Python layer
> > - Rebased on master
> >
> > v3->v4:
> > - Remove unrelated change in 8/10
> > - Remove a double test case
> > - Add David's tags
> >
> > v2->v3:
> > - Move Orientation out of CameraConfiguration to a dedicated file
> > - Change operator*(Transform, Transform)
> > - Address David's comments in documentation and commit messages
> >
> > v1->v2:
> > - Provide a better definitio of Orientation based on two basic operations
> > - Provide functions to combine Orientation and Transpose
> > - Provide tests
> > - Do not adjust properties::Rotation anymore
> >
> > This series proposes to replace the usage to Transform in the public API in
> > favour of a new Orientation type, defined based on the EXIF
> > specification, tag 274, 'orientation'. For reference this is the same as
> > implemented by gstreamer:
> > https://gstreamer.freedesktop.org/documentation/gstreamer/gsttaglist.html?gi-language=c#GST_TAG_IMAGE_ORIENTATION
> >
> > (the names of the enum values are different as gstreamer expresses the
> > "correction" while we express the actual orientation in memory buffers).
> >
> > The newly introduced CameraConfiguration::orientation replaces the
> > existing CameraConfiguration::tranform, and it is meant for application to
> > express how they would like the images to be oriented, not to tell libcamera
> > what to do. As an example, passing in 'rotation0' means that the application
> > expects the images to be rotated upright, and doesn't tell libcamera not to
> > apply any rotation like passing in "Transform::Identity" did.
> >
> > The value CameraConfiguration::orientation is set to after a validate() also
> > differs in meaning, as instead of reporting "what applications have to do
> > to obtain what they originally asked for" it simply reports the actual
> > orientation of the stream: this means that if libcamera cannot fully satisfy the
> > user request it will set ::orientation to report the native images rotation
> > and the CameraConfiguration::status will be set to Adjusted.
> >
> > Handling of 90 and 270 degrees rotation has also changed: as the camera sensor
> > cannot correct rotations that include a transposition, requests for a 90/270
> > corrections are ignored. This makes it clear and less confusing for applications
> > that they have to deal with correction fully by themselves. As an example, with
> > the current implementation if the application requires a Rot270 (HFlip +
> > Transpose) libcamera will do the HFlip and leave transposition to the upper
> > layers. There is no clear advantage in doing so, and display compositors are
> > better suited for handling transpositions and flipping in a single pass instead
> > of having the library try to handle part of that.
> >
> > This series clearly breaks the application API as it removes a member from
> > CameraConfiguration, so it should be introduced probably only when a new release
> > is cut.
> >
> > Tested on RPi imx219 with the newly introduced cam option, flip, mirror and
> > rotation work as expected.
>
> Overall this looks good to me. I've just sent a series of patches on top
> of thes series (numbered 13/12 to 21/12), with the first patches being
> small fixups that I propose squashing into the corresponding base patch.
> The last three patches would require more complex rework of your series
> if we wanted to squash them, so I think we can keep them on top.
>
> I wanted to replace the
>
> Orientation operator*(const Orientation &o, const Transform &t);
>
> with a
>
> Orientation Transform::operator()(const Orientation &o);
>
> in order to be able to write
>
> 	Transform t = ...;
> 	Orientation o1 = ...,
> 	Orientation o2 = t(o1);
>
> or also
>
> 	Orientation o1 = ...,
> 	Orientation o2 = Transform::HFlip(o1);
>
> which I think would be a nicer syntax. However, I couldn't find a way to
> nicely do so :-( Transform is currently an enum class, which is not a
> class, and thus can't have member operators. The call operator having no
> non-member (outside of the class definition) variant, I had to try and
> turn Transform into a class. I got stuck at the point where HFlip had to
> be a static constexpr member of the Transform class (to be able to write
> `Transform::HFlip`), but also an instance of the Transform class (to be
> able to write `Transform::HFlip(o)`). The compiler rightfully complained
> that I was trying to use a type that hasn't been fully defined yet. I
> gave up for now (but would be very interested into hearing from a C++
> enthousiast on how something similar could be achieved). If we keep the
> multiplication operator as-is, then your patch 06/12 that inverts the
> order of the operands to the multiplication operator between two
> Transform instances is needed.
>
> If you're fine with the additional changes, I can handle the squashing
> and pushing. I'll need R-b tags for the last three patches though. As
> for this series, with the changes on top,

Minor comments apart, I'm fine with all of this.

With a few questions clarified, feel free to squash in what's
squashable and push

Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

Thanks
   j

>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>
> An additional change I would like to see on top would be the rewrite of
> the Python Transform class in Python, without going through C++
> bindings. This would allow us to drop the transformFromRotation() which
> is used in the Python bindings code only. Any opinion on this ?
>
> > Jacopo Mondi (12):
> >   libcamera: camera_sensor: Cache rotationTransform_
> >   libcamera: camera: Introduce Orientation
> >   Documentation: Add figures to document Orientation
> >   libcamera: properties: Make 'Rotation' the mounting rotation
> >   libcamera: transform: Add functions to convert Orientation
> >   libcamera: transform: Invert operator*() operands
> >   libcamera: transform: Add operations with Orientation
> >   test: Add unit test for Transform and Orientation
> >   py: libcamera: Define and use Orientation
> >   libcamera: Use CameraConfiguration::orientation
> >   apps: cam: Add option to set stream orientation
> >   py: cam: Add option to set stream orientation
> >
> >  Documentation/Doxyfile.in                     |   2 +
> >  Documentation/rotation/rotate0.svg            | 132 +++++++
> >  Documentation/rotation/rotate0Mirror.svg      | 135 +++++++
> >  Documentation/rotation/rotate180.svg          | 135 +++++++
> >  Documentation/rotation/rotate180Mirror.svg    | 135 +++++++
> >  Documentation/rotation/rotate270.svg          | 135 +++++++
> >  Documentation/rotation/rotate270Mirror.svg    | 135 +++++++
> >  Documentation/rotation/rotate90.svg           | 135 +++++++
> >  Documentation/rotation/rotate90Mirror.svg     | 135 +++++++
> >  include/libcamera/camera.h                    |   4 +-
> >  include/libcamera/internal/camera_sensor.h    |   5 +-
> >  include/libcamera/meson.build                 |   1 +
> >  include/libcamera/orientation.h               |  28 ++
> >  include/libcamera/transform.h                 |   7 +
> >  src/apps/cam/camera_session.cpp               |  18 +
> >  src/apps/cam/main.cpp                         |   5 +
> >  src/apps/cam/main.h                           |   1 +
> >  src/libcamera/camera.cpp                      |  20 +-
> >  src/libcamera/camera_sensor.cpp               | 131 ++++---
> >  src/libcamera/meson.build                     |   1 +
> >  src/libcamera/orientation.cpp                 |  79 +++++
> >  src/libcamera/pipeline/ipu3/ipu3.cpp          |   6 +-
> >  src/libcamera/pipeline/rkisp1/rkisp1.cpp      |   8 +-
> >  .../pipeline/rpi/common/pipeline_base.cpp     |   9 +-
> >  src/libcamera/pipeline/simple/simple.cpp      |   6 +-
> >  src/libcamera/pipeline/uvcvideo/uvcvideo.cpp  |   4 +-
> >  src/libcamera/pipeline/vimc/vimc.cpp          |   4 +-
> >  src/libcamera/property_ids.yaml               |   8 +-
> >  src/libcamera/transform.cpp                   | 112 +++++-
> >  src/py/cam/cam.py                             |  21 ++
> >  src/py/libcamera/py_enums.cpp                 |  10 +
> >  src/py/libcamera/py_main.cpp                  |   2 +-
> >  test/meson.build                              |   1 +
> >  test/transform.cpp                            | 329 ++++++++++++++++++
> >  34 files changed, 1787 insertions(+), 112 deletions(-)
> >  create mode 100644 Documentation/rotation/rotate0.svg
> >  create mode 100644 Documentation/rotation/rotate0Mirror.svg
> >  create mode 100644 Documentation/rotation/rotate180.svg
> >  create mode 100644 Documentation/rotation/rotate180Mirror.svg
> >  create mode 100644 Documentation/rotation/rotate270.svg
> >  create mode 100644 Documentation/rotation/rotate270Mirror.svg
> >  create mode 100644 Documentation/rotation/rotate90.svg
> >  create mode 100644 Documentation/rotation/rotate90Mirror.svg
> >  create mode 100644 include/libcamera/orientation.h
> >  create mode 100644 src/libcamera/orientation.cpp
> >  create mode 100644 test/transform.cpp
>
> --
> Regards,
>
> Laurent Pinchart
Barnabás Pőcze Nov. 3, 2023, 7:41 p.m. UTC | #3
Hi


2023. október 23., hétfő 0:56 keltezéssel, Laurent Pinchart via libcamera-devel <libcamera-devel@lists.libcamera.org> írta:

> [...]
> I wanted to replace the
> 
> Orientation operator*(const Orientation &o, const Transform &t);
> 
> with a
> 
> Orientation Transform::operator()(const Orientation &o);
> 
> in order to be able to write
> 
> 	Transform t = ...;
> 	Orientation o1 = ...,
> 	Orientation o2 = t(o1);
> 
> or also
> 
> 	Orientation o1 = ...,
> 	Orientation o2 = Transform::HFlip(o1);
> 
> which I think would be a nicer syntax. However, I couldn't find a way to
> nicely do so :-( Transform is currently an enum class, which is not a
> class, and thus can't have member operators. The call operator having no
> non-member (outside of the class definition) variant, I had to try and
> turn Transform into a class. I got stuck at the point where HFlip had to
> be a static constexpr member of the Transform class (to be able to write
> `Transform::HFlip`), but also an instance of the Transform class (to be
> able to write `Transform::HFlip(o)`). The compiler rightfully complained
> that I was trying to use a type that hasn't been fully defined yet. I
> gave up for now (but would be very interested into hearing from a C++
> enthousiast on how something similar could be achieved). If we keep the
> multiplication operator as-is, then your patch 06/12 that inverts the
> order of the operands to the multiplication operator between two
> Transform instances is needed.
> [...]

I believe something like the following can work: https://gcc.godbolt.org/z/ecqoGTGGs
It's a lot of code compared to the enum...

On a slightly related note, I think using a pipe is also not bad: "o1 | t1 | t2 | t3 ...".


Regards,
Barnabás Pőcze