@@ -81,6 +81,20 @@ public static GCHandleDiposable ToMat<T>(this Matrix<T> matrix, MatType matType,
81
81
82
82
public static GCHandleDiposable ToMat < T > ( this Volume < T > volume , MatType matType , int elementSize , out CvMat result )
83
83
=> volume . Data . ToMat ( volume . Size . XY , volume . Delta . XY , volume . FirstIndex , matType , elementSize , out result ) ;
84
+
85
+ public static Scalar ToScalar < T > ( this T [ ] color , Col . Format format )
86
+ {
87
+ var result = new Scalar ( ) ;
88
+
89
+ if ( color != null )
90
+ {
91
+ var order = format . ChannelOrder ( ) ;
92
+ for ( var i = 0 ; i < Fun . Min ( color . Length , format . ChannelCount ( ) ) ; i ++ )
93
+ result [ i ] = Convert . ToDouble ( color [ order [ i ] ] ) ;
94
+ }
95
+
96
+ return result ;
97
+ }
84
98
}
85
99
86
100
#endregion
@@ -89,7 +103,7 @@ public sealed class PixProcessor : IPixProcessor
89
103
{
90
104
public string Name => "OpenCV" ;
91
105
92
- public PixProcessorCaps Capabilities => PixProcessorCaps . Scale | PixProcessorCaps . Remap ;
106
+ public PixProcessorCaps Capabilities => PixProcessorCaps . Scale | PixProcessorCaps . Rotate | PixProcessorCaps . Remap ;
93
107
94
108
[ OnAardvarkInit ]
95
109
public static void Init ( )
@@ -135,10 +149,74 @@ public PixImage<T> Scale<T>(PixImage<T> image, V2d scaleFactor, ImageInterpolati
135
149
136
150
#region Rotate
137
151
152
+ // TODO: Change the interface to accept T[] as border value
153
+ /// <inheritdoc cref="Rotate{T}(PixImage{T}, double, bool, ImageInterpolation, ImageBorderType, T)"/>
138
154
public PixImage < T > Rotate < T > ( PixImage < T > image , double angleInRadians , bool resize , ImageInterpolation interpolation ,
139
155
ImageBorderType borderType = ImageBorderType . Const ,
140
- T border = default )
141
- => null ;
156
+ T [ ] border = default )
157
+ {
158
+ var src = image . Volume ;
159
+
160
+ if ( ! src . HasImageWindowLayout ( ) )
161
+ {
162
+ throw new ArgumentException ( $ "Volume must be in image layout (Delta = { src . Delta } ).") ;
163
+ }
164
+
165
+ var matType = typeof ( T ) . ToMatType ( image . ChannelCount ) ;
166
+ var elementSize = typeof ( T ) . GetCLRSize ( ) ;
167
+
168
+ var srcCenter = image . Size . ToV2d ( ) * 0.5 ;
169
+
170
+ // This already takes the handedness of the image coordinate system into account.
171
+ // See: https://docs.opencv.org/4.10.0/da/d54/group__imgproc__transform.html#gafbbc470ce83812914a70abfb604f4326
172
+ var rotMat =
173
+ Cv2 . GetRotationMatrix2D (
174
+ new Point2f ( ( float ) srcCenter . X , ( float ) srcCenter . Y ) ,
175
+ - angleInRadians . DegreesFromRadians ( ) ,
176
+ 1.0
177
+ ) ;
178
+
179
+ var dstSize = image . Volume . Size ;
180
+ if ( resize )
181
+ {
182
+ // Compute bounds of rotated image
183
+ // See: https://stackoverflow.com/questions/3231176/how-to-get-size-of-a-rotated-rectangle
184
+ var cos = rotMat . At < double > ( 0 , 0 ) ;
185
+ var sin = rotMat . At < double > ( 1 , 0 ) ;
186
+ var cosAbs = cos . Abs ( ) ;
187
+ var sinAbs = sin . Abs ( ) ;
188
+ dstSize . X = ( long ) ( image . Width * cosAbs + image . Height * sinAbs + 0.5 ) ;
189
+ dstSize . Y = ( long ) ( image . Width * sinAbs + image . Height * cosAbs + 0.5 ) ;
190
+
191
+ // Adjust transformation matrix for new center
192
+ // We describe the inverse transformation (i.e. from dst to src).
193
+ // Shift by -dstCenter -> rotate CW -> shift by srcCenter.
194
+ // See: https://math.stackexchange.com/questions/2093314/rotation-matrix-of-rotation-around-a-point-other-than-the-origin
195
+ var dstCenter = dstSize . XY . ToV2d ( ) * 0.5 ;
196
+ rotMat . At < double > ( 0 , 2 ) = - dstCenter . X * cos + dstCenter . Y * sin + srcCenter . X ;
197
+ rotMat . At < double > ( 1 , 2 ) = - dstCenter . X * sin - dstCenter . Y * cos + srcCenter . Y ;
198
+ }
199
+
200
+ var dst = dstSize . CreateImageVolume < T > ( ) ;
201
+
202
+ using var _src = src . ToMat ( matType , elementSize , out var srcMat ) ;
203
+ using var _dst = dst . ToMat ( matType , elementSize , out var dstMat ) ;
204
+
205
+ Cv2 . WarpAffine (
206
+ srcMat , dstMat , rotMat ,
207
+ new Size ( ( int ) dst . SX , ( int ) dst . SY ) ,
208
+ interpolation . ToInterpolationFlags ( false ) | InterpolationFlags . WarpInverseMap ,
209
+ borderType . ToBorderTypes ( ) ,
210
+ ( borderType == ImageBorderType . Const ) ? border . ToScalar ( image . Format ) : new ( )
211
+ ) ;
212
+
213
+ return new ( image . Format , dst ) ;
214
+ }
215
+
216
+ public PixImage < T > Rotate < T > ( PixImage < T > image , double angleInRadians , bool resize , ImageInterpolation interpolation ,
217
+ ImageBorderType borderType ,
218
+ T border )
219
+ => Rotate ( image , angleInRadians , resize , interpolation , borderType , new T [ ] { border , border , border , border } ) ;
142
220
143
221
#endregion
144
222
@@ -173,20 +251,11 @@ public PixImage<T> Remap<T>(PixImage<T> image, Matrix<float> mapX, Matrix<float>
173
251
using var _x = mapX . ToMat ( MatType . CV_32FC1 , 4 , out var mapXMat ) ;
174
252
using var _y = mapY . ToMat ( MatType . CV_32FC1 , 4 , out var mapYMat ) ;
175
253
176
- Scalar borderValue = new ( ) ;
177
- if ( borderType == ImageBorderType . Const && border != null )
178
- {
179
- var order = image . Format . ChannelOrder ( ) ;
180
-
181
- for ( var i = 0 ; i < Fun . Min ( border . Length , 4 ) ; i ++ )
182
- borderValue [ i ] = Convert . ToDouble ( border [ order [ i ] ] ) ;
183
- }
184
-
185
254
Cv2 . Remap (
186
255
srcMat , dstMat , mapXMat , mapYMat ,
187
256
interpolation . ToInterpolationFlags ( false ) ,
188
257
borderType . ToBorderTypes ( ) ,
189
- borderValue
258
+ ( borderType == ImageBorderType . Const ) ? border . ToScalar ( image . Format ) : new ( )
190
259
) ;
191
260
192
261
return new ( image . Format , dst ) ;
0 commit comments