Message ID | 20180404153024.56015-1-vittorio.giovara@gmail.com |
---|---|

State | New |

Headers | show |

On Wed, Apr 04, 2018 at 05:30:24PM +0200, Vittorio Giovara wrote: > The transformation operations that can be described by a display matrix > are not limited to pure rotation, but include horizontal and vertical > flip, as well as transpose and antitranspose. Unfortunately the current > API can only return a rotation angle in degrees, and is not designed to > detect flip operations or a combination of rotation and flip. > > So implement an additional API to analyze the display matrix and return > the most common rotation operations (multiples of 90º) as well flips or > a combination thereof. This function returns a bitfield mask composed of > AVDisplayOrientation elements that describe which rendering operations > should be performed on the frame. The existing API is still available > and useful in case of custom rotations. > > Signed-off-by: Vittorio Giovara <vittorio.giovara@gmail.com> > --- > Note: the new operations describe a clockwise rotation, while the > old API provided a counterclockwise rotation. I always felt this was > a mistake as it's counterintuitive and suprising to new users, so I > didn't want to cargo-cult it to a new API. What do people think about it? > > See also https://github.com/FFMS/ffms2/issues/317 for flipped samples, code, > and additional discussion. > > Missing changelog entry and version bump. > Vittorio > > libavutil/display.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > libavutil/display.h | 53 ++++++++++++++++++++++++++++++ > 2 files changed, 145 insertions(+) It might be more usefull to fully factorize the matrix than these special cases for example the matrix can be described by these 4 scalars 1. rotation 2. horizontal scale 3. vertical scale 4. shear (there are more parameters like translation and z specific changes but IIUC these have no meaning ?) Note fliping in above would be a negative scale value shear could be specified as the angle between the x/y basis vectors The reason i suggest this is that these 4 values are easier to understand for a human and should allow reconstructing an equivalent transform matrix. While at the same time not limiting things to a subset of special cases [...]

On Wed, Apr 4, 2018 at 8:30 AM, Vittorio Giovara <vittorio.giovara@gmail.com > wrote: > The transformation operations that can be described by a display matrix > are not limited to pure rotation, but include horizontal and vertical > flip, as well as transpose and antitranspose. Unfortunately the current > API can only return a rotation angle in degrees, and is not designed to > detect flip operations or a combination of rotation and flip. > > So implement an additional API to analyze the display matrix and return > the most common rotation operations (multiples of 90º) as well flips or > a combination thereof. This function returns a bitfield mask composed of > AVDisplayOrientation elements that describe which rendering operations > should be performed on the frame. The existing API is still available > and useful in case of custom rotations. > > Signed-off-by: Vittorio Giovara <vittorio.giovara@gmail.com> > --- > Note: the new operations describe a clockwise rotation, while the > old API provided a counterclockwise rotation. I always felt this was > a mistake as it's counterintuitive and suprising to new users, so I > didn't want to cargo-cult it to a new API. What do people think about it? > i am more used to counter-clockwise angle representation just like zero-indexing, but either way is fine with me. > See also https://github.com/FFMS/ffms2/issues/317 for flipped samples, > code, > and additional discussion. > > Missing changelog entry and version bump. > Vittorio > > libavutil/display.c | 92 ++++++++++++++++++++++++++++++ > +++++++++++++++++++++++ > libavutil/display.h | 53 ++++++++++++++++++++++++++++++ > 2 files changed, 145 insertions(+) > > diff --git a/libavutil/display.c b/libavutil/display.c > index f7500948ff..839961ec20 100644 > --- a/libavutil/display.c > +++ b/libavutil/display.c > @@ -22,6 +22,7 @@ > #include <string.h> > #include <math.h> > > +#include "avstring.h" > #include "display.h" > #include "mathematics.h" > > @@ -73,3 +74,94 @@ void av_display_matrix_flip(int32_t matrix[9], int > hflip, int vflip) > for (i = 0; i < 9; i++) > matrix[i] *= flip[i % 3]; > } > + > +uint32_t av_display_orientation_get(int32_t matrix_src[9]) > +{ > + int32_t matrix[9]; > + uint32_t orientation = 0; > + int64_t det = (int64_t)matrix_src[0] * matrix_src[4] - > (int64_t)matrix_src[1] * matrix_src[3]; > + > + /* Duplicate matrix so that the input one is not modified in case of > flip. */ > + memcpy(matrix, matrix_src, sizeof(*matrix_src) * 9); > + > + if (det < 0) { > + /* Always assume an horizontal flip for simplicity, it can be > + * changed later if rotation is 180º. */ > + orientation = AV_FLIP_HORIZONTAL; > + av_display_matrix_flip(matrix, 1, 0); > + } > + > + if (matrix[1] == (1 << 16) && matrix[3] == -(1 << 16)) { > + orientation |= AV_ROTATION_90; > + } else if (matrix[0] == -(1 << 16) && matrix[4] == -(1 << 16)) { > + if (det < 0) > + orientation = AV_FLIP_VERTICAL; > + else > + orientation |= AV_ROTATION_180; > + } else if (matrix[1] == -(1 << 16) && matrix[3] == (1 << 16)) { > + orientation |= AV_ROTATION_270; > + } else if (matrix[0] == (1 << 16) && matrix[4] == (1 << 16)) { > + orientation |= AV_IDENTITY; > + } else { > + orientation |= AV_ROTATION_CUSTOM; > + } > + > + return orientation; > +} > + > +void av_display_orientation_set(int32_t matrix[9], uint32_t orientation, > double angle) > +{ > + int hflip = !!(orientation & AV_FLIP_HORIZONTAL); > + int vflip = !!(orientation & AV_FLIP_VERTICAL); > + > + memset(matrix, 0, sizeof(*matrix) * 9); > + matrix[8] = 1 << 30; > + > + if (orientation & AV_IDENTITY) { > + matrix[0] = 1 << 16; > + matrix[4] = 1 << 16; > + } else if (orientation & AV_ROTATION_90) { > + matrix[1] = 1 << 16; > + matrix[3] = -(1 << 16); > + } else if (orientation & AV_ROTATION_180) { > + matrix[0] = -(1 << 16); > + matrix[4] = -(1 << 16); > + } else if (orientation & AV_ROTATION_270) { > + matrix[1] = -(1 << 16); > + matrix[3] = 1 << 16; > + } else if (orientation & AV_ROTATION_CUSTOM) { > + av_display_rotation_set(matrix, angle); > + } > + > + av_display_matrix_flip(matrix, hflip, vflip); > +} > + > +void av_display_orientation_name(uint32_t orientation, char *buf, size_t > buf_size) > +{ > + if (orientation == 0) { > + av_strlcpy(buf, "identity", buf_size); > + return; > + } > + > + if (orientation & AV_ROTATION_90) > + av_strlcpy(buf, "rotation_90", buf_size); > + else if (orientation & AV_ROTATION_180) > + av_strlcpy(buf, "rotation_180", buf_size); > + else if (orientation & AV_ROTATION_270) > + av_strlcpy(buf, "rotation_270", buf_size); > + else if (orientation & AV_ROTATION_CUSTOM) > + av_strlcpy(buf, "rotation_custom", buf_size); > + else > + buf[0] = '\0'; > + > + if (orientation & AV_FLIP_HORIZONTAL) { > + if (buf[0] != '\0') > + av_strlcat(buf, "+", buf_size); > + av_strlcat(buf, "hflip", buf_size); > + } > The order of the FLIP and rotation are important. While computing flip , we are assuming that FLIP is applied first, and then rotation. So while showing the name also, flip should be shown first and then rotation. > + if (orientation & AV_FLIP_VERTICAL) { > + if (buf[0] != '\0') > + av_strlcat(buf, "+", buf_size); > + av_strlcat(buf, "vflip", buf_size); > + } > +} > diff --git a/libavutil/display.h b/libavutil/display.h > index 2d869fcd16..a057453b20 100644 > --- a/libavutil/display.h > +++ b/libavutil/display.h > @@ -27,6 +27,7 @@ > #define AVUTIL_DISPLAY_H > > #include <stdint.h> > +#include <stddef.h> > > /** > * @addtogroup lavu_video > @@ -105,6 +106,58 @@ void av_display_rotation_set(int32_t matrix[9], > double angle); > */ > void av_display_matrix_flip(int32_t matrix[9], int hflip, int vflip); > > +enum AVDisplayOrientation { > + /** There is no orientation operation to be performed on the frame. */ > + AV_IDENTITY = 0, > + /** Apply a 90º clockwise rotation on the frame. */ > + AV_ROTATION_90 = 1 << 0, > + /** Apply a 180º clockwise rotation on the frame. */ > + AV_ROTATION_180 = 1 << 1, > + /** Apply a 270º clockwise rotation on the frame. */ > + AV_ROTATION_270 = 1 << 2, > + /** > + * Apply a custom rotation on the frame. Users may inspect the input > matrix > + * with av_display_rotation_get() to know the degree amount. > + * > + * @note av_display_rotation_get() returns a counterclockwise angle. > + */ > + AV_ROTATION_CUSTOM = 1 << 3, > + /** Apply a horizontal flip on the frame. */ > Comment should also state that FLIP should be applied before rotation. > + AV_FLIP_HORIZONTAL = 1 << 4, > + /** Apply a vertical flip on the frame. */ > + AV_FLIP_VERTICAL = 1 << 5, > +}; > + > +/** > + * Return a mask composed of AVDisplayOrientation elements describing the > list > + * of operations to be performed on the rendered video frame from a given > + * transformation matrix. > + * > + * @param matrix an allocated transformation matrix > + * @return a set of AVDisplayOrientation operations > + */ > +uint32_t av_display_orientation_get(int32_t matrix[9]); > + > +/** > + * Initialize a transformation matrix describing a set of > AVDisplayOrientation > + * operations. If a custom rotation is desired, it is possible to set the > rotation > + * angle as optional parameter. > + * > + * @param matrix an allocated transformation matrix (will be fully > overwritten > + * by this function) > + * @param orientation a set of AVDisplayOrientation operations > + * @param angle counterclockwise rotation angle in degrees > + */ > +void av_display_orientation_set(int32_t matrix[9], uint32_t orientation, > double angle); > + > +/** > + * Return a human readable description of the input AVDisplayOrientation > set. > + * > + * @param orientation a set of AVDisplayOrientation operations > + * @param buf a user-allocated buffer that will contain the returned > string > + * @param buf_size size in bytes of the buffer > + */ > +void av_display_orientation_name(uint32_t orientation, char *buf, size_t > size); > /** > * @} > * @} > -- > 2.16.2 > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel >

--- > On Wed, Apr 04, 2018 at 05:30:24PM +0200, Vittorio Giovara wrote: > > libavutil/display.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > > libavutil/display.h | 53 ++++++++++++++++++++++++++++++ > > 2 files changed, 145 insertions(+) > It might be more usefull to fully factorize the matrix than these special cases > > for example the matrix can be described by these 4 scalars > 1. rotation > 2. horizontal scale > 3. vertical scale > 4. shear With the proposed API the matrix is already factorized: a mask is returned containing a set of operation. Special cases such as custom rotation is already factorized, as in you need to call an additional function on the same matrix to know more about the rotation angle. On the other hand the most common cases are already ready to be used right away. Do you think it should just expose the fact that it is a rotation and always require the user to inspect the matrix again? Note that hflip and vflip can't be described with pure rotation angles. I feel like this would complicate the API. For any missing operation, such as transpose, shear, and scaling, this API should be extensible enoughe, so that if anybody wants to add enum values and functions to retrieve the desired operation, it can be done. > (there are more parameters like translation and z specific changes but IIUC > these have no meaning ?) Correct given that video right now is a 2d surface we can ignore anything related to the depth axis. > Note fliping in above would be a negative scale value > shear could be specified as the angle between the x/y basis vectors > > The reason i suggest this is that these 4 values are easier to understand > for a human and should allow reconstructing an equivalent transform matrix. > While at the same time not limiting things to a subset of special cases > The "special cases" are also the most common operations that every player implements so I think it makes sense to have them readily available, with as few calls as possible. > -- > Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB (I'm trying something different wrt my mail client, I hope it doesn't botch the threaded reply again).

On Thu, Apr 05, 2018 at 01:51:10PM +0200, Vittorio Giovara wrote: > --- > > On Wed, Apr 04, 2018 at 05:30:24PM +0200, Vittorio Giovara wrote: > > > libavutil/display.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > > > libavutil/display.h | 53 ++++++++++++++++++++++++++++++ > > > 2 files changed, 145 insertions(+) > > > It might be more usefull to fully factorize the matrix than these special cases > > > > for example the matrix can be described by these 4 scalars > > 1. rotation > > 2. horizontal scale > > 3. vertical scale > > 4. shear > > With the proposed API the matrix is already factorized: a mask is returned > containing a set of operation. Special cases such as custom rotation is > already factorized, as in you need to call an additional function on the > same matrix to know more about the rotation angle. On the other hand the > most common cases are already ready to be used right away. no, the proposed API is not equivalent if thats what you mean. What i suggest gives a single unique factorization represented by 4 scalar values The API proposed prior does not gurantee a unique result. Instead a single matrix can be represented by a almost infinite number of operations In practice this means you cannot test the factorization by a simple equality check as a single matrix can be factored in many different ways. Also the prior API has a issue with order of operations. These operations are not commutative, the order of flip and rotate makes a big difference. And at this point i do not see what it can even be used for. The rotation angle is different for example depending on what other operations are done before and after the rotation for a matrix. [...] > > > (there are more parameters like translation and z specific changes but IIUC > > these have no meaning ?) > > Correct given that video right now is a 2d surface we can ignore anything > related to the depth axis. > > > Note fliping in above would be a negative scale value > > shear could be specified as the angle between the x/y basis vectors > > > > The reason i suggest this is that these 4 values are easier to understand > > for a human and should allow reconstructing an equivalent transform matrix. > > While at the same time not limiting things to a subset of special cases > > > > The "special cases" are also the most common operations that every player > implements so I think it makes sense to have them readily available, with > as few calls as possible. With what i suggest, not sure i explained it well enough there would be a single call to provide a struct or array of 4 scalars a 90 degree to the right rotation would for example have a {90, 1, 1, 0} and could be checked for by memcmp() or a more specialized function that returns a scalar "difference" so testing for these common operations should be very simple and compact. Testing for another angle of rotation or other transform would be similarly simple. [...]

Yes the order of operations is a problem in a generic matrix, but for a display matrix the order is more or less consolidated in a defacto standard: check for flip first, then rotation. We have the same pattern in h264 and hevc decoder for the rotation side data. You are right that the description of the API does not convey the order and that it should be better documented, although I don't have a specific standard to quote (besides what is already mentioned in display.h). > > The "special cases" are also the most common operations that every player > > implements so I think it makes sense to have them readily available, with > > as few calls as possible. > > With what i suggest, not sure i explained it well enough > there would be a single call to provide a struct or array of 4 scalars > a 90 degree to the right rotation would for example have a > {90, 1, 1, 0} and could be checked for by memcmp() or a more specialized > function that returns a scalar "difference" > so testing for these common operations should be very simple and compact. > Testing for another angle of rotation or other transform would be similarly > simple. Well the only downside of that is that we are replacing a well-known set of instructions coded in a 3x3 matrix with a ffmpeg-only 4x1 array of integers. You would need special code and documentation to parse either the matrix or the array, so it kinda defies my attempt at simplifying the API: I feel like having a negative scale factor to represent a flip is as complex as having euclidean math to compute the rotation angle when the allowed orientations are just 4. On the other hand, I'm not strongly advocating for one way of another, if you could maybe point me to the right direction and share some code on how you mean the matrix parsing should be carried out I'll try to prepare a second version of this patch. Vittorio

On 6 April 2018 at 17:59, Vittorio Giovara <vittorio.giovara@gmail.com> wrote: > Yes the order of operations is a problem in a generic matrix, but for a > display matrix the order is more or less consolidated in a defacto > standard: > check for flip first, then rotation. We have the same pattern in h264 and > hevc decoder for the rotation side data. > > You are right that the description of the API does not convey the order > and that it should be better documented, although I don't have a specific > standard to quote (besides what is already mentioned in display.h). > > > > The "special cases" are also the most common operations that every > player > > > implements so I think it makes sense to have them readily available, > with > > > as few calls as possible. > > > > With what i suggest, not sure i explained it well enough > > there would be a single call to provide a struct or array of 4 scalars > > a 90 degree to the right rotation would for example have a > > {90, 1, 1, 0} and could be checked for by memcmp() or a more specialized > > function that returns a scalar "difference" > > so testing for these common operations should be very simple and compact. > > Testing for another angle of rotation or other transform would be > similarly > > simple. > > > Well the only downside of that is that we are replacing a well-known set of > instructions coded in a 3x3 matrix with a ffmpeg-only 4x1 array of > integers. > You would need special code and documentation to parse either the matrix or > the array, so it kinda defies my attempt at simplifying the API: I feel > like > having a negative scale factor to represent a flip is as complex as having > euclidean math to compute the rotation angle when the allowed orientations > are just 4. > > On the other hand, I'm not strongly advocating for one way of another, if > you could maybe point me to the right direction and share some code on how > you mean the matrix parsing should be carried out I'll try to prepare a > second version of this patch. > > Vittorio > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel > I agree, I'd rather have a standard 3x3 matrix exposed than making something new up.

diff --git a/libavutil/display.c b/libavutil/display.c index f7500948ff..839961ec20 100644 --- a/libavutil/display.c +++ b/libavutil/display.c @@ -22,6 +22,7 @@ #include <string.h> #include <math.h> +#include "avstring.h" #include "display.h" #include "mathematics.h" @@ -73,3 +74,94 @@ void av_display_matrix_flip(int32_t matrix[9], int hflip, int vflip) for (i = 0; i < 9; i++) matrix[i] *= flip[i % 3]; } + +uint32_t av_display_orientation_get(int32_t matrix_src[9]) +{ + int32_t matrix[9]; + uint32_t orientation = 0; + int64_t det = (int64_t)matrix_src[0] * matrix_src[4] - (int64_t)matrix_src[1] * matrix_src[3]; + + /* Duplicate matrix so that the input one is not modified in case of flip. */ + memcpy(matrix, matrix_src, sizeof(*matrix_src) * 9); + + if (det < 0) { + /* Always assume an horizontal flip for simplicity, it can be + * changed later if rotation is 180º. */ + orientation = AV_FLIP_HORIZONTAL; + av_display_matrix_flip(matrix, 1, 0); + } + + if (matrix[1] == (1 << 16) && matrix[3] == -(1 << 16)) { + orientation |= AV_ROTATION_90; + } else if (matrix[0] == -(1 << 16) && matrix[4] == -(1 << 16)) { + if (det < 0) + orientation = AV_FLIP_VERTICAL; + else + orientation |= AV_ROTATION_180; + } else if (matrix[1] == -(1 << 16) && matrix[3] == (1 << 16)) { + orientation |= AV_ROTATION_270; + } else if (matrix[0] == (1 << 16) && matrix[4] == (1 << 16)) { + orientation |= AV_IDENTITY; + } else { + orientation |= AV_ROTATION_CUSTOM; + } + + return orientation; +} + +void av_display_orientation_set(int32_t matrix[9], uint32_t orientation, double angle) +{ + int hflip = !!(orientation & AV_FLIP_HORIZONTAL); + int vflip = !!(orientation & AV_FLIP_VERTICAL); + + memset(matrix, 0, sizeof(*matrix) * 9); + matrix[8] = 1 << 30; + + if (orientation & AV_IDENTITY) { + matrix[0] = 1 << 16; + matrix[4] = 1 << 16; + } else if (orientation & AV_ROTATION_90) { + matrix[1] = 1 << 16; + matrix[3] = -(1 << 16); + } else if (orientation & AV_ROTATION_180) { + matrix[0] = -(1 << 16); + matrix[4] = -(1 << 16); + } else if (orientation & AV_ROTATION_270) { + matrix[1] = -(1 << 16); + matrix[3] = 1 << 16; + } else if (orientation & AV_ROTATION_CUSTOM) { + av_display_rotation_set(matrix, angle); + } + + av_display_matrix_flip(matrix, hflip, vflip); +} + +void av_display_orientation_name(uint32_t orientation, char *buf, size_t buf_size) +{ + if (orientation == 0) { + av_strlcpy(buf, "identity", buf_size); + return; + } + + if (orientation & AV_ROTATION_90) + av_strlcpy(buf, "rotation_90", buf_size); + else if (orientation & AV_ROTATION_180) + av_strlcpy(buf, "rotation_180", buf_size); + else if (orientation & AV_ROTATION_270) + av_strlcpy(buf, "rotation_270", buf_size); + else if (orientation & AV_ROTATION_CUSTOM) + av_strlcpy(buf, "rotation_custom", buf_size); + else + buf[0] = '\0'; + + if (orientation & AV_FLIP_HORIZONTAL) { + if (buf[0] != '\0') + av_strlcat(buf, "+", buf_size); + av_strlcat(buf, "hflip", buf_size); + } + if (orientation & AV_FLIP_VERTICAL) { + if (buf[0] != '\0') + av_strlcat(buf, "+", buf_size); + av_strlcat(buf, "vflip", buf_size); + } +} diff --git a/libavutil/display.h b/libavutil/display.h index 2d869fcd16..a057453b20 100644 --- a/libavutil/display.h +++ b/libavutil/display.h @@ -27,6 +27,7 @@ #define AVUTIL_DISPLAY_H #include <stdint.h> +#include <stddef.h> /** * @addtogroup lavu_video @@ -105,6 +106,58 @@ void av_display_rotation_set(int32_t matrix[9], double angle); */ void av_display_matrix_flip(int32_t matrix[9], int hflip, int vflip); +enum AVDisplayOrientation { + /** There is no orientation operation to be performed on the frame. */ + AV_IDENTITY = 0, + /** Apply a 90º clockwise rotation on the frame. */ + AV_ROTATION_90 = 1 << 0, + /** Apply a 180º clockwise rotation on the frame. */ + AV_ROTATION_180 = 1 << 1, + /** Apply a 270º clockwise rotation on the frame. */ + AV_ROTATION_270 = 1 << 2, + /** + * Apply a custom rotation on the frame. Users may inspect the input matrix + * with av_display_rotation_get() to know the degree amount. + * + * @note av_display_rotation_get() returns a counterclockwise angle. + */ + AV_ROTATION_CUSTOM = 1 << 3, + /** Apply a horizontal flip on the frame. */ + AV_FLIP_HORIZONTAL = 1 << 4, + /** Apply a vertical flip on the frame. */ + AV_FLIP_VERTICAL = 1 << 5, +}; + +/** + * Return a mask composed of AVDisplayOrientation elements describing the list + * of operations to be performed on the rendered video frame from a given + * transformation matrix. + * + * @param matrix an allocated transformation matrix + * @return a set of AVDisplayOrientation operations + */ +uint32_t av_display_orientation_get(int32_t matrix[9]); + +/** + * Initialize a transformation matrix describing a set of AVDisplayOrientation + * operations. If a custom rotation is desired, it is possible to set the rotation + * angle as optional parameter. + * + * @param matrix an allocated transformation matrix (will be fully overwritten + * by this function) + * @param orientation a set of AVDisplayOrientation operations + * @param angle counterclockwise rotation angle in degrees + */ +void av_display_orientation_set(int32_t matrix[9], uint32_t orientation, double angle); + +/** + * Return a human readable description of the input AVDisplayOrientation set. + * + * @param orientation a set of AVDisplayOrientation operations + * @param buf a user-allocated buffer that will contain the returned string + * @param buf_size size in bytes of the buffer + */ +void av_display_orientation_name(uint32_t orientation, char *buf, size_t size); /** * @} * @}

`The transformation operations that can be described by a display matrix are not limited to pure rotation, but include horizontal and vertical flip, as well as transpose and antitranspose. Unfortunately the current API can only return a rotation angle in degrees, and is not designed to detect flip operations or a combination of rotation and flip. So implement an additional API to analyze the display matrix and return the most common rotation operations (multiples of 90º) as well flips or a combination thereof. This function returns a bitfield mask composed of AVDisplayOrientation elements that describe which rendering operations should be performed on the frame. The existing API is still available and useful in case of custom rotations. Signed-off-by: Vittorio Giovara <vittorio.giovara@gmail.com> --- Note: the new operations describe a clockwise rotation, while the old API provided a counterclockwise rotation. I always felt this was a mistake as it's counterintuitive and suprising to new users, so I didn't want to cargo-cult it to a new API. What do people think about it? See also https://github.com/FFMS/ffms2/issues/317 for flipped samples, code, and additional discussion. Missing changelog entry and version bump. Vittorio libavutil/display.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ libavutil/display.h | 53 ++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+)`