|
| 1 | +// Copyright (c) Six Labors. |
| 2 | +// Licensed under the Apache License, Version 2.0. |
| 3 | + |
| 4 | +using System.Numerics; |
| 5 | +using System.Runtime.CompilerServices; |
| 6 | +using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
| 7 | +using SixLabors.ImageSharp.Processing; |
| 8 | + |
| 9 | +namespace SixLabors.ImageSharp.Web |
| 10 | +{ |
| 11 | + /// <summary> |
| 12 | + /// Contains various helper methods for working with EXIF orientation values. |
| 13 | + /// </summary> |
| 14 | + public static class ExifOrientationUtilities |
| 15 | + { |
| 16 | + /// <summary> |
| 17 | + /// Transforms the specified vector value depending on the specified <paramref name="orientation" />. |
| 18 | + /// </summary> |
| 19 | + /// <param name="position">The input vector value.</param> |
| 20 | + /// <param name="min">The minimum bounds of the area of interest.</param> |
| 21 | + /// <param name="max">The maximum bounds of the area of interest.</param> |
| 22 | + /// <param name="orientation">The EXIF orientation.</param> |
| 23 | + /// <returns> |
| 24 | + /// The transformed vector. |
| 25 | + /// </returns> |
| 26 | + public static Vector2 Transform(Vector2 position, Vector2 min, Vector2 max, ushort orientation) |
| 27 | + { |
| 28 | + if (orientation is >= ExifOrientationMode.Unknown and <= ExifOrientationMode.TopLeft) |
| 29 | + { |
| 30 | + return position; |
| 31 | + } |
| 32 | + |
| 33 | + // New XY is calculated based on flipping and rotating the input XY. |
| 34 | + // Coordinates are normalized to a range of 0-1 so we can pass a constant integer size to the transform builder. |
| 35 | + Vector2 normalized = Normalize(position, min, max); |
| 36 | + AffineTransformBuilder builder = new(); |
| 37 | + Size size = new(1, 1); |
| 38 | + switch (orientation) |
| 39 | + { |
| 40 | + case ExifOrientationMode.TopRight: |
| 41 | + builder.AppendTranslation(new Vector2(size.Width - normalized.X, 0)); |
| 42 | + break; |
| 43 | + case ExifOrientationMode.BottomRight: |
| 44 | + builder.AppendRotationDegrees(180); |
| 45 | + break; |
| 46 | + case ExifOrientationMode.BottomLeft: |
| 47 | + builder.AppendTranslation(new Vector2(0, size.Height - normalized.Y)); |
| 48 | + break; |
| 49 | + case ExifOrientationMode.LeftTop: |
| 50 | + builder.AppendTranslation(new Vector2(size.Width - normalized.X, 0)); |
| 51 | + builder.AppendRotationDegrees(270); |
| 52 | + break; |
| 53 | + case ExifOrientationMode.RightTop: |
| 54 | + builder.AppendRotationDegrees(270); |
| 55 | + break; |
| 56 | + case ExifOrientationMode.RightBottom: |
| 57 | + builder.AppendTranslation(new Vector2(size.Width - normalized.X, 0)); |
| 58 | + builder.AppendRotationDegrees(90); |
| 59 | + break; |
| 60 | + case ExifOrientationMode.LeftBottom: |
| 61 | + builder.AppendRotationDegrees(90); |
| 62 | + break; |
| 63 | + default: |
| 64 | + return position; |
| 65 | + } |
| 66 | + |
| 67 | + Matrix3x2 matrix = builder.BuildMatrix(size); |
| 68 | + return DeNormalize(Vector2.Transform(normalized, matrix), min, max); |
| 69 | + } |
| 70 | + |
| 71 | + /// <summary> |
| 72 | + /// Transforms the specified size value depending on the specified <paramref name="orientation" />. |
| 73 | + /// </summary> |
| 74 | + /// <param name="size">The input size value.</param> |
| 75 | + /// <param name="orientation">The EXIF orientation.</param> |
| 76 | + /// <returns> |
| 77 | + /// The transformed size. |
| 78 | + /// </returns> |
| 79 | + public static Size Transform(Size size, ushort orientation) |
| 80 | + => IsExifOrientationRotated(orientation) |
| 81 | + ? new Size(size.Height, size.Width) |
| 82 | + : size; |
| 83 | + |
| 84 | + /// <summary> |
| 85 | + /// Transforms the specified anchor value depending on the specified <paramref name="orientation" />. |
| 86 | + /// </summary> |
| 87 | + /// <param name="anchor">The input anchor value.</param> |
| 88 | + /// <param name="orientation">The EXIF orientation.</param> |
| 89 | + /// <returns> |
| 90 | + /// The transformed anchor. |
| 91 | + /// </returns> |
| 92 | + public static AnchorPositionMode Transform(AnchorPositionMode anchor, ushort orientation) |
| 93 | + { |
| 94 | + if (orientation is >= ExifOrientationMode.Unknown and <= ExifOrientationMode.TopLeft) |
| 95 | + { |
| 96 | + return anchor; |
| 97 | + } |
| 98 | + |
| 99 | + /* |
| 100 | + New anchor position is determined by calculating the direction of the anchor relative to the source image. |
| 101 | + In the following example, the TopRight anchor becomes BottomRight |
| 102 | +
|
| 103 | + T L |
| 104 | + +-----------------------+ +--------------+ |
| 105 | + | *| | | |
| 106 | + | | | | |
| 107 | + L | TL | R | | |
| 108 | + | | | | |
| 109 | + | | B | LB | T |
| 110 | + | | | | |
| 111 | + +-----------------------+ | | |
| 112 | + B | | |
| 113 | + | | |
| 114 | + | *| |
| 115 | + +--------------+ |
| 116 | + R |
| 117 | + */ |
| 118 | + return anchor switch |
| 119 | + { |
| 120 | + AnchorPositionMode.Center => anchor, |
| 121 | + AnchorPositionMode.Top => orientation switch |
| 122 | + { |
| 123 | + ExifOrientationMode.BottomLeft or ExifOrientationMode.BottomRight => AnchorPositionMode.Bottom, |
| 124 | + ExifOrientationMode.LeftTop or ExifOrientationMode.RightTop => AnchorPositionMode.Left, |
| 125 | + ExifOrientationMode.LeftBottom or ExifOrientationMode.RightBottom => AnchorPositionMode.Right, |
| 126 | + _ => anchor, |
| 127 | + }, |
| 128 | + AnchorPositionMode.Bottom => orientation switch |
| 129 | + { |
| 130 | + ExifOrientationMode.BottomLeft or ExifOrientationMode.BottomRight => AnchorPositionMode.Top, |
| 131 | + ExifOrientationMode.LeftTop or ExifOrientationMode.RightTop => AnchorPositionMode.Right, |
| 132 | + ExifOrientationMode.LeftBottom or ExifOrientationMode.RightBottom => AnchorPositionMode.Left, |
| 133 | + _ => anchor, |
| 134 | + }, |
| 135 | + AnchorPositionMode.Left => orientation switch |
| 136 | + { |
| 137 | + ExifOrientationMode.TopRight or ExifOrientationMode.BottomRight => AnchorPositionMode.Right, |
| 138 | + ExifOrientationMode.LeftTop or ExifOrientationMode.LeftBottom => AnchorPositionMode.Top, |
| 139 | + ExifOrientationMode.RightTop or ExifOrientationMode.RightBottom => AnchorPositionMode.Bottom, |
| 140 | + _ => anchor, |
| 141 | + }, |
| 142 | + AnchorPositionMode.Right => orientation switch |
| 143 | + { |
| 144 | + ExifOrientationMode.TopRight or ExifOrientationMode.BottomRight => AnchorPositionMode.Left, |
| 145 | + ExifOrientationMode.LeftTop or ExifOrientationMode.LeftBottom => AnchorPositionMode.Bottom, |
| 146 | + ExifOrientationMode.RightTop or ExifOrientationMode.RightBottom => AnchorPositionMode.Top, |
| 147 | + _ => anchor, |
| 148 | + }, |
| 149 | + AnchorPositionMode.TopLeft => orientation switch |
| 150 | + { |
| 151 | + ExifOrientationMode.TopRight or ExifOrientationMode.LeftBottom => AnchorPositionMode.TopRight, |
| 152 | + ExifOrientationMode.BottomRight or ExifOrientationMode.RightTop => AnchorPositionMode.BottomLeft, |
| 153 | + ExifOrientationMode.BottomLeft or ExifOrientationMode.RightBottom => AnchorPositionMode.BottomRight, |
| 154 | + _ => anchor, |
| 155 | + }, |
| 156 | + AnchorPositionMode.TopRight => orientation switch |
| 157 | + { |
| 158 | + ExifOrientationMode.TopRight or ExifOrientationMode.RightTop => AnchorPositionMode.TopLeft, |
| 159 | + ExifOrientationMode.BottomLeft or ExifOrientationMode.LeftBottom => AnchorPositionMode.BottomRight, |
| 160 | + ExifOrientationMode.BottomRight or ExifOrientationMode.LeftTop => AnchorPositionMode.BottomLeft, |
| 161 | + _ => anchor, |
| 162 | + }, |
| 163 | + AnchorPositionMode.BottomRight => orientation switch |
| 164 | + { |
| 165 | + ExifOrientationMode.TopRight or ExifOrientationMode.LeftBottom => AnchorPositionMode.BottomLeft, |
| 166 | + ExifOrientationMode.BottomLeft or ExifOrientationMode.RightTop => AnchorPositionMode.TopRight, |
| 167 | + ExifOrientationMode.BottomRight or ExifOrientationMode.RightBottom => AnchorPositionMode.TopLeft, |
| 168 | + _ => anchor, |
| 169 | + }, |
| 170 | + AnchorPositionMode.BottomLeft => orientation switch |
| 171 | + { |
| 172 | + ExifOrientationMode.TopRight or ExifOrientationMode.RightTop => AnchorPositionMode.BottomRight, |
| 173 | + ExifOrientationMode.BottomLeft or ExifOrientationMode.LeftBottom => AnchorPositionMode.TopLeft, |
| 174 | + ExifOrientationMode.BottomRight or ExifOrientationMode.LeftTop => AnchorPositionMode.TopRight, |
| 175 | + _ => anchor, |
| 176 | + }, |
| 177 | + _ => anchor, |
| 178 | + }; |
| 179 | + } |
| 180 | + |
| 181 | + /// <summary> |
| 182 | + /// Returns a value indicating whether an EXIF orientation is rotated (not flipped). |
| 183 | + /// </summary> |
| 184 | + /// <param name="orientation">The EXIF orientation.</param> |
| 185 | + /// <returns><see langword="true"/> if the orientation value indicates rotation; otherwise <see langword="false"/>.</returns> |
| 186 | + public static bool IsExifOrientationRotated(ushort orientation) |
| 187 | + => orientation switch |
| 188 | + { |
| 189 | + ExifOrientationMode.LeftTop |
| 190 | + or ExifOrientationMode.RightTop |
| 191 | + or ExifOrientationMode.RightBottom |
| 192 | + or ExifOrientationMode.LeftBottom => true, |
| 193 | + _ => false, |
| 194 | + }; |
| 195 | + |
| 196 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 197 | + private static Vector2 Normalize(Vector2 x, Vector2 min, Vector2 max) => (x - min) / (max - min); |
| 198 | + |
| 199 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 200 | + private static Vector2 DeNormalize(Vector2 x, Vector2 min, Vector2 max) => min + (x * (max - min)); |
| 201 | + } |
| 202 | +} |
0 commit comments