@@ -328,6 +328,140 @@ internal static void Ignore<T>(T _) { }
328
328
329
329
#endregion
330
330
331
+ #region Processors
332
+
333
+ private static readonly Dictionary < IPixProcessor , int > s_processors = new ( )
334
+ {
335
+ { PixProcessor . Instance , 1 }
336
+ } ;
337
+
338
+ /// <summary>
339
+ /// Sets the priority of a PixImage processor.
340
+ /// The priority determines the order in which proocessors are invoked to scale, rotate, or remap an image.
341
+ /// Processors with higher priority are invoked first.
342
+ /// If the processor does not exist, it is added with the given priority.
343
+ /// </summary>
344
+ /// <param name="processor">The processor to modify.</param>
345
+ /// <param name="priority">The priority to set.</param>
346
+ public static void SetProcessor ( IPixProcessor processor , int priority )
347
+ {
348
+ lock ( s_processors )
349
+ {
350
+ s_processors [ processor ] = priority ;
351
+ }
352
+ }
353
+
354
+ /// <summary>
355
+ /// Adds a PixImage processor.
356
+ /// Assigns a priority that is greater than the highest priority among existing processors, resulting in a LIFO order.
357
+ /// If the processor already exists, the priority is modified.
358
+ /// </summary>
359
+ /// <param name="processor">The processor to add.</param>
360
+ public static void AddProcessor ( IPixProcessor processor )
361
+ {
362
+ lock ( s_processors )
363
+ {
364
+ var maxPriority = s_processors . Values . Max ( - 1 ) ;
365
+ s_processors [ processor ] = maxPriority + 1 ;
366
+ }
367
+ }
368
+
369
+ /// <summary>
370
+ /// Removes a PixImage processor.
371
+ /// </summary>
372
+ /// <param name="processor">The processor to remove.</param>
373
+ public static void RemoveProcessor ( IPixProcessor processor )
374
+ {
375
+ lock ( s_processors ) { s_processors . Remove ( processor ) ; }
376
+ }
377
+
378
+ /// <summary>
379
+ /// Gets a dictionary of registered processors with their associated priority.
380
+ /// Only returns processors that have at least the given minimum capabilities.
381
+ /// </summary>
382
+ /// <param name="minCapabilities">The minimum capabilities for a processor to be considered.</param>
383
+ /// <returns>A dictionary of registered processors.</returns>
384
+ public static Dictionary < IPixProcessor , int > GetProcessorsWithPriority ( PixProcessorCaps minCapabilities = PixProcessorCaps . None )
385
+ {
386
+ lock ( s_processors )
387
+ {
388
+ var result = new Dictionary < IPixProcessor , int > ( ) ;
389
+
390
+ foreach ( var p in s_processors )
391
+ {
392
+ if ( p . Key . Capabilities . HasFlag ( minCapabilities ) )
393
+ result [ p . Key ] = p . Value ;
394
+ }
395
+
396
+ return result ;
397
+ }
398
+ }
399
+
400
+ /// <summary>
401
+ /// Gets a list of registered processors sorted by priority in descending order.
402
+ /// Only returns processors that have at least the given minimum capabilities.
403
+ /// </summary>
404
+ /// <param name="minCapabilities">The minimum capabilities for a processor to be considered.</param>
405
+ /// <returns>A list of registered processors.</returns>
406
+ public static List < IPixProcessor > GetProcessors ( PixProcessorCaps minCapabilities = PixProcessorCaps . None )
407
+ {
408
+ lock ( s_processors )
409
+ {
410
+ var list = new List < KeyValuePair < IPixProcessor , int > > ( ) ;
411
+
412
+ foreach ( var p in s_processors )
413
+ {
414
+ if ( p . Key . Capabilities . HasFlag ( minCapabilities ) )
415
+ list . Add ( p ) ;
416
+ }
417
+
418
+ list . Sort ( ( x , y ) => y . Value - x . Value ) ;
419
+ return list . Map ( x => x . Key ) ;
420
+ }
421
+ }
422
+
423
+ internal static PixImage < T > InvokeProcessors < T > (
424
+ Func < IPixProcessor , PixImage < T > > invoke ,
425
+ PixProcessorCaps minCapabilities ,
426
+ string operationDescription )
427
+ {
428
+ PixImage < T > result ;
429
+
430
+ foreach ( var p in GetProcessors ( minCapabilities ) )
431
+ {
432
+ try
433
+ {
434
+ result = invoke ( p ) ;
435
+ if ( result != null ) return result ;
436
+ }
437
+ catch ( Exception e )
438
+ {
439
+ Report . Warn ( $ "Failed to { operationDescription } with { p . Name } image processor: { e . Message } ") ;
440
+ }
441
+ }
442
+
443
+ var processors = GetProcessors ( PixProcessorCaps . None ) ;
444
+ var errorMessage = $ "Cannot { operationDescription } ";
445
+
446
+ if ( processors . Count == 0 )
447
+ {
448
+ errorMessage += ", no image processors available!" ;
449
+ }
450
+ else
451
+ {
452
+ errorMessage += ", available image processors:" + Environment . NewLine ;
453
+
454
+ foreach ( var p in processors )
455
+ {
456
+ errorMessage += $ " - { p . Name } : { p . Capabilities } " + Environment . NewLine ;
457
+ }
458
+ }
459
+
460
+ throw new NotSupportedException ( errorMessage ) ;
461
+ }
462
+
463
+ #endregion
464
+
331
465
#region Constructors
332
466
333
467
static PixImage ( )
@@ -1333,29 +1467,45 @@ public IEnumerable<Matrix<T>> Channels
1333
1467
1334
1468
#region Image Manipulation
1335
1469
1470
+ #region Transformed
1471
+
1336
1472
public override PixImage TransformedPixImage ( ImageTrafo trafo )
1337
1473
=> Transformed ( trafo ) ;
1338
1474
1339
1475
public PixImage < T > Transformed ( ImageTrafo trafo )
1340
1476
=> new PixImage < T > ( Format , Volume . Transformed ( trafo ) ) ;
1341
1477
1478
+ #endregion
1479
+
1480
+ #region Remapped
1481
+
1342
1482
public override PixImage RemappedPixImage ( Matrix < float > xMap , Matrix < float > yMap , ImageInterpolation ip = ImageInterpolation . Cubic )
1343
1483
=> Remapped ( xMap , xMap , ip ) ;
1344
1484
1345
1485
public PixImage < T > Remapped ( Matrix < float > xMap , Matrix < float > yMap , ImageInterpolation ip = ImageInterpolation . Cubic )
1346
1486
{
1347
- if ( s_remappedFun == null )
1348
- {
1349
- throw new NotSupportedException ( $ "No remapping function has been installed via PixImage<{ ( typeof ( T ) . Name ) } >.SetRemappedFun") ;
1350
- }
1487
+ return InvokeProcessors (
1488
+ ( p ) => p . Remap ( this , xMap , yMap , ip , default ) ,
1489
+ PixProcessorCaps . Remap , "remap image"
1490
+ ) ;
1491
+ }
1351
1492
1352
- return new PixImage < T > ( Format , s_remappedFun ( Volume , xMap , yMap , ip ) ) ;
1493
+ [ Obsolete ( "Use the PixImage processor API instead." ) ]
1494
+ public static void SetRemappedFun ( Func < Volume < T > , Matrix < float > , Matrix < float > , ImageInterpolation , Volume < T > > remappedFun )
1495
+ {
1496
+ LegacyPixProcessor . Instance . SetRemapFun < T > (
1497
+ ( remappedFun == null ) ? null : ( pi , xMap , yMap , ip ) => new ( pi . Format , remappedFun ( pi . Volume , xMap , yMap , ip ) )
1498
+ ) ;
1499
+
1500
+ if ( LegacyPixProcessor . Instance . Capabilities != PixProcessorCaps . None )
1501
+ SetProcessor ( LegacyPixProcessor . Instance , 0 ) ;
1502
+ else
1503
+ RemoveProcessor ( LegacyPixProcessor . Instance ) ;
1353
1504
}
1354
1505
1355
- private static Func < Volume < T > , Matrix < float > , Matrix < float > , ImageInterpolation , Volume < T > > s_remappedFun = null ;
1506
+ #endregion
1356
1507
1357
- public static void SetRemappedFun ( Func < Volume < T > , Matrix < float > , Matrix < float > , ImageInterpolation , Volume < T > > remappedFun )
1358
- => s_remappedFun = remappedFun ;
1508
+ #region Resized
1359
1509
1360
1510
public override PixImage ResizedPixImage ( V2i newSize , ImageInterpolation ip = ImageInterpolation . Cubic )
1361
1511
=> Scaled ( ( V2d ) newSize / ( V2d ) Size , ip ) ;
@@ -1366,47 +1516,68 @@ public PixImage<T> Resized(V2i newSize, ImageInterpolation ip = ImageInterpolati
1366
1516
public PixImage < T > Resized ( int xSize , int ySize , ImageInterpolation ip = ImageInterpolation . Cubic )
1367
1517
=> Scaled ( new V2d ( xSize , ySize ) / ( V2d ) Size , ip ) ;
1368
1518
1519
+ #endregion
1520
+
1521
+ #region Rotated
1522
+
1369
1523
public override PixImage RotatedPixImage ( double angleInRadiansCCW , bool resize = true , ImageInterpolation ip = ImageInterpolation . Cubic )
1370
1524
=> Rotated ( angleInRadiansCCW , resize , ip ) ;
1371
1525
1372
1526
public PixImage < T > Rotated ( double angleInRadiansCCW , bool resize = true , ImageInterpolation ip = ImageInterpolation . Cubic )
1373
1527
{
1374
- if ( s_rotatedFun == null )
1375
- {
1376
- throw new NotSupportedException ( $ "No rotating function has been installed via PixImage<{ ( typeof ( T ) . Name ) } >.SetRotatedFun") ;
1377
- }
1528
+ return InvokeProcessors (
1529
+ ( p ) => p . Rotate ( this , angleInRadiansCCW , resize , ip , default ) ,
1530
+ PixProcessorCaps . Rotate , "rotate image"
1531
+ ) ;
1532
+ }
1378
1533
1379
- return new PixImage < T > ( Format , s_rotatedFun ( Volume , angleInRadiansCCW , resize , ip ) ) ;
1534
+ [ Obsolete ( "Use the PixImage processor API instead." ) ]
1535
+ public static void SetRotatedFun ( Func < Volume < T > , double , bool , ImageInterpolation , Volume < T > > rotatedFun )
1536
+ {
1537
+ LegacyPixProcessor . Instance . SetRotateFun < T > (
1538
+ ( rotatedFun == null ) ? null : ( pi , angle , resize , ip ) => new ( pi . Format , rotatedFun ( pi . Volume , angle , resize , ip ) )
1539
+ ) ;
1540
+
1541
+ if ( LegacyPixProcessor . Instance . Capabilities != PixProcessorCaps . None )
1542
+ SetProcessor ( LegacyPixProcessor . Instance , 0 ) ;
1543
+ else
1544
+ RemoveProcessor ( LegacyPixProcessor . Instance ) ;
1380
1545
}
1381
1546
1382
- private static Func < Volume < T > , double , bool , ImageInterpolation , Volume < T > > s_rotatedFun = null ;
1547
+ #endregion
1383
1548
1384
- public static void SetRotatedFun ( Func < Volume < T > , double , bool , ImageInterpolation , Volume < T > > rotatedFun )
1385
- => s_rotatedFun = rotatedFun ;
1549
+ #region Scaled
1386
1550
1387
1551
public override PixImage ScaledPixImage ( V2d scaleFactor , ImageInterpolation ip = ImageInterpolation . Cubic )
1388
1552
=> Scaled ( scaleFactor , ip ) ;
1389
1553
1390
1554
public PixImage < T > Scaled ( V2d scaleFactor , ImageInterpolation ip = ImageInterpolation . Cubic )
1391
1555
{
1392
- if ( s_scaledFun == null )
1393
- {
1394
- throw new NotSupportedException ( $ "No scaling function has been installed via PixImage<{ ( typeof ( T ) . Name ) } >.SetScaledFun") ;
1395
- }
1396
-
1397
- if ( ! ( scaleFactor . X > 0.0 && scaleFactor . Y > 0.0 ) ) throw new ArgumentOutOfRangeException ( nameof ( scaleFactor ) ) ;
1556
+ if ( scaleFactor . AnySmallerOrEqual ( 0 ) )
1557
+ throw new ArgumentOutOfRangeException ( $ "Scale factor must be positive ({ scaleFactor } ).") ;
1398
1558
1399
1559
// SuperSample is only available for scale factors < 1; fall back to Cubic
1400
- if ( ( scaleFactor . X >= 1.0 || scaleFactor . Y >= 1.0 ) && ip == ImageInterpolation . SuperSample )
1560
+ if ( scaleFactor . AnyGreater ( 1.0 ) && ip == ImageInterpolation . SuperSample )
1401
1561
ip = ImageInterpolation . Cubic ;
1402
1562
1403
- return new PixImage < T > ( Format , s_scaledFun ( Volume , scaleFactor , ip ) ) ;
1563
+ return InvokeProcessors (
1564
+ ( p ) => p . Scale ( this , scaleFactor , ip ) ,
1565
+ PixProcessorCaps . Scale , "scale image"
1566
+ ) ;
1404
1567
}
1405
1568
1406
- private static Func < Volume < T > , V2d , ImageInterpolation , Volume < T > > s_scaledFun = TensorExtensions . Scaled ;
1407
-
1569
+ [ Obsolete ( "Use the PixImage processor API instead." ) ]
1408
1570
public static void SetScaledFun ( Func < Volume < T > , V2d , ImageInterpolation , Volume < T > > scaledFun )
1409
- => s_scaledFun = scaledFun ;
1571
+ {
1572
+ LegacyPixProcessor . Instance . SetScaleFun < T > (
1573
+ ( scaledFun == null ) ? null : ( pi , scaleFactor , ip ) => new ( pi . Format , scaledFun ( pi . Volume , scaleFactor , ip ) )
1574
+ ) ;
1575
+
1576
+ if ( LegacyPixProcessor . Instance . Capabilities != PixProcessorCaps . None )
1577
+ SetProcessor ( LegacyPixProcessor . Instance , 0 ) ;
1578
+ else
1579
+ RemoveProcessor ( LegacyPixProcessor . Instance ) ;
1580
+ }
1410
1581
1411
1582
public PixImage < T > Scaled ( double scaleFactor , ImageInterpolation ip = ImageInterpolation . Cubic )
1412
1583
=> Scaled ( new V2d ( scaleFactor , scaleFactor ) , ip ) ;
@@ -1416,6 +1587,8 @@ public PixImage<T> Scaled(double xScaleFactor, double yScaleFactor, ImageInterpo
1416
1587
1417
1588
#endregion
1418
1589
1590
+ #endregion
1591
+
1419
1592
#region SubImages
1420
1593
1421
1594
/// <summary>
0 commit comments