![]() |
VOOZH | about |
dotnet add package mostlylucid.ephemeral.atoms.imagesharp --version 2.6.3
NuGet\Install-Package mostlylucid.ephemeral.atoms.imagesharp -Version 2.6.3
<PackageReference Include="mostlylucid.ephemeral.atoms.imagesharp" Version="2.6.3" />
<PackageVersion Include="mostlylucid.ephemeral.atoms.imagesharp" Version="2.6.3" />Directory.Packages.props
<PackageReference Include="mostlylucid.ephemeral.atoms.imagesharp" />Project file
paket add mostlylucid.ephemeral.atoms.imagesharp --version 2.6.3
#r "nuget: mostlylucid.ephemeral.atoms.imagesharp, 2.6.3"
#:package mostlylucid.ephemeral.atoms.imagesharp@2.6.3
#addin nuget:?package=mostlylucid.ephemeral.atoms.imagesharp&version=2.6.3Install as a Cake Addin
#tool nuget:?package=mostlylucid.ephemeral.atoms.imagesharp&version=2.6.3Install as a Cake Tool
ImageSharp integration atoms for signal-based image processing pipelines
This package provides production-ready atoms for image processing using ImageSharp with Ephemeral's signal-based coordination.
Loads images from disk with file I/O tracking.
Signals:
image.loading - Before load startsimage.loaded - After successful loadimage.dimensions:{width}x{height} - Image sizeimage.format:{format} - Image formatimage.load.failed - On errorimage.error:{message} - Error detailsCreates multiple sized variants (thumbnail, medium, large) sequentially.
Signals:
resize.started - Pipeline beginsresize.count:{n} - Number of sizesresize.{size}.started - Per-size startresize.{size}.complete - Per-size completionfile.saved:{path} - File writtenresize.complete - All sizes doneConfiguration:
new ResizeOptions
{
Sizes = new List<(Size, string)>
{
(new Size(150, 150), "thumb"),
(new Size(800, 600), "medium"),
(new Size(1920, 1080), "large")
},
JpegQuality = 90
}
Nested Coordinator Pattern - Creates multiple sized variants using bounded parallel execution. Demonstrates how an atom can internally use a coordinator to manage concurrent work while propagating operation-scoped signals.
Signals:
resize.parallel.started - Parallel pipeline beginsresize.parallelism:{n} - Max parallel resizesresize.{size}.started - Per-size start (with sub-operation ID)resize.{size}.complete - Per-size completion (with sub-operation ID)file.saved:{path} - File written (with sub-operation ID)resize.parallel.complete - All sizes doneConfiguration:
new ParallelResizeOptions
{
Sizes = new List<(Size, string)>
{
(new Size(150, 150), "thumb"),
(new Size(800, 600), "medium"),
(new Size(1920, 1080), "large")
},
JpegQuality = 90,
MaxParallelism = 3 // Process 3 resizes concurrently
}
Pattern Highlight: Each resize operation becomes a sub-operation with its own operation ID. The coordinator window
is configured as maxParallelism * 3 for short-lived operations. Signals from sub-operations propagate to the main
SignalSink with proper operation IDs, enabling precise tracking and control.
Adds EXIF metadata to images.
Signals:
exif.processing - Processing startedexif.{size}.started - Per-image startexif.{size}.complete - Per-image doneexif.written:{size} - Metadata writtenexif.complete - All images processedConfiguration:
new ExifOptions
{
Copyright = "© 2025 Your Company",
Software = "Your App Name",
Artist = "Artist Name",
Keywords = new List<string> { "keyword1", "keyword2" }
}
Adds text watermarks to images.
Signals:
watermark.started - Processing beginswatermark.rendering - Watermark being drawnwatermark.complete - Watermark addedwatermark.applied:{path} - File savedprocessing.complete - Pipeline completeimage.pipeline.complete:{n} - Image numberConfiguration:
new WatermarkOptions
{
Text = "Your Watermark",
FontFamily = "Arial",
FontSize = 48,
Color = (255, 255, 255),
Opacity = 128,
Quality = 95,
TargetSize = "large",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
Padding = 80f
}
using Mostlylucid.Ephemeral;
using Mostlylucid.Ephemeral.Atoms.ImageSharp;
var sink = new SignalSink();
await using var pipeline = new ImagePipeline(sink)
.WithLoader()
.WithResize()
.WithExif()
.WithWatermark();
var job = new ImageJob(
sourcePath: "input.jpg",
outputDir: "output",
batchNumber: 0,
imageNumber: 1
);
var result = await pipeline.ProcessAsync(job);
Console.WriteLine($"Processed in {result.ProcessingTime}");
Console.WriteLine($"Thumbnail: {result.ThumbnailPath}");
Console.WriteLine($"Medium: {result.MediumPath}");
Console.WriteLine($"Large: {result.LargePath}");
Console.WriteLine($"Watermarked: {result.WatermarkedPath}");
await using var pipeline = new ImagePipeline(sink)
.WithLoader()
.WithResize(new ResizeOptions
{
Sizes = new List<(Size, string)>
{
(new Size(200, 200), "small"),
(new Size(1024, 768), "hd")
},
JpegQuality = 95
})
.WithExif(new ExifOptions
{
Copyright = "© 2025 MyCompany",
Artist = "Photo Bot"
})
.WithWatermark(new WatermarkOptions
{
Text = "MyBrand",
FontSize = 60,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom
});
var sink = new SignalSink();
// Subscribe to see sub-operation signals
sink.Subscribe(signal =>
{
if (signal.Signal.StartsWith("resize."))
{
var opId = signal.OperationId.HasValue ? $"(op:{signal.OperationId})" : "";
Console.WriteLine($"{signal.Signal} {opId}");
}
});
await using var pipeline = new ImagePipeline(sink)
.WithLoader()
.WithParallelResize(new ParallelResizeOptions
{
Sizes = new List<(Size, string)>
{
(new Size(100, 100), "tiny"),
(new Size(200, 200), "small"),
(new Size(400, 400), "medium"),
(new Size(800, 800), "large"),
(new Size(1920, 1920), "xlarge")
},
MaxParallelism = 3, // Process 3 resizes concurrently
JpegQuality = 90
});
var result = await pipeline.ProcessAsync(job);
// Each resize gets its own operation ID
// Signals show: resize.tiny.started (op:123), resize.small.started (op:124), etc.
var sink = new SignalSink();
sink.Subscribe(signal =>
{
if (signal.Signal.StartsWith("image."))
{
Console.WriteLine($"Image event: {signal.Signal}");
}
else if (signal.Signal.StartsWith("resize."))
{
Console.WriteLine($"Resize event: {signal.Signal}");
}
});
await using var pipeline = new ImagePipeline(sink)
.WithLoader()
.WithResize();
await pipeline.ProcessAsync(job);
Enable clean cancellation of image processing operations via operation-scoped signals:
var sink = new SignalSink();
// Use within an EphemeralWorkCoordinator to get operation context
var coordinator = new EphemeralWorkCoordinator<ImageJob>(sink, async (job, op, ct) =>
{
// Enable cancellation hook - listens for '{opid}.imagesharp.stop' signal
await using var pipeline = new ImagePipeline(sink)
.WithCancellationHook(op.Id, maxDimension: 5000) // <-- Operation-scoped
.WithLoader()
.WithResize()
.WithExif()
.WithWatermark();
return await pipeline.ProcessAsync(job, ct);
});
coordinator.Enqueue(new ImageJob("huge.jpg", "output", 0, 1));
// From another coordinator watching signals:
// When it sees image.dimensions:6000x4000 for operation 123
sink.Raise("imagesharp.stop", operationId: 123); // Cancels ONLY operation 123
Pattern 1: Explicit Stop Signal
// Another coordinator/watcher decides to stop this operation
sink.Raise("imagesharp.stop", operationId: 123);
Pattern 2: Automatic Dimension-Based Cancellation
// Pipeline emits: image.dimensions:6000x4000 (opid: 123)
// Hook configured with maxDimension: 5000 sees this
// Automatically cancels because 6000 > 5000
await using var pipeline = new ImagePipeline(sink)
.WithCancellationHook(op.Id, maxDimension: 5000) // Auto-cancel large images
.WithLoader()
.WithResize();
Signals emitted by cancellation:
imagesharp.dimension.exceeded:{w}x{h} - Image too large (opid-scoped)imagesharp.stopping - Cancellation initiated (opid-scoped)imagesharp.stopped - Cancellation token triggered (opid-scoped)pipeline.cancelled:{n} - Specific image cancelledHow it works:
WithCancellationHook(opId, maxDim) creates operation-scoped hooksignal.OperationId == opIdimagesharp.stop OR image.dimensions: exceeding maxExample: Cross-Operation Coordination
// Supervisor coordinator watches all image operations
var supervisor = new SignalSink();
supervisor.Subscribe(signal =>
{
// Watch for dimension signals
if (signal.Signal.StartsWith("image.dimensions:"))
{
var dims = signal.Signal.Substring("image.dimensions:".Length);
var parts = dims.Split('x');
if (int.Parse(parts[0]) > 10000 || int.Parse(parts[1]) > 10000)
{
// Cancel this specific operation
sink.Raise("imagesharp.stop", signal.OperationId);
Console.WriteLine($"Cancelled operation {signal.OperationId} - image too large");
}
}
});
var sink = new SignalSink();
await using var pipeline = new ImagePipeline(sink)
.WithLoader()
.WithResize()
.WithExif()
.WithWatermark();
var jobs = new List<ImageJob>();
for (int i = 0; i < 100; i++)
{
jobs.Add(new ImageJob($"input{i}.jpg", "output", 0, i));
}
// Process with bounded concurrency
var semaphore = new SemaphoreSlim(4, 4);
var tasks = jobs.Select(async job =>
{
await semaphore.WaitAsync();
try
{
return await pipeline.ProcessAsync(job);
}
finally
{
semaphore.Release();
}
});
var results = await Task.WhenAll(tasks);
Flows through the pipeline, holding state and emitting signals.
Properties:
Job - Original job specificationImage - Loaded image (disposed automatically)Outputs - Dictionary of size name → output pathOriginalSize - Input file size in bytesWidth, Height, Format - Image metadataMethods:
ToResult() - Converts to ImageProcessingResultDisposeAsync() - Cleans up image resourcesFluent builder for configuring processing pipeline.
Builder Methods:
WithLoader() - Add load atomWithResize(options?) - Add resize atomWithExif(options?) - Add EXIF atomWithWatermark(options?) - Add watermark atomExecution:
ProcessAsync(job, ct) - Execute pipeline on jobAll atoms follow the Pure Notification pattern:
Signals are notifications, not state carriers. The ImageProcessingContext is the source of truth.
SixLabors.ImageSharp - Core image processingSixLabors.ImageSharp.Drawing - Watermark text renderingMostlylucid.Ephemeral - Signal infrastructureUnlicense - Public Domain
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 1 NuGet packages that depend on mostlylucid.ephemeral.atoms.imagesharp:
| Package | Downloads |
|---|---|
|
mostlylucid.ephemeral.complete
Meta-package that references all Mostlylucid.Ephemeral packages - bounded async execution with signals, atoms, and patterns. Install this single package to get everything. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.6.3 | 103 | 5/22/2026 |
| 2.6.2 | 105 | 5/22/2026 |
| 2.5.1 | 101 | 5/22/2026 |
| 2.5.0 | 101 | 5/3/2026 |
| 2.4.0 | 117 | 4/17/2026 |
| 2.3.2 | 139 | 1/9/2026 |
| 2.3.1 | 130 | 1/9/2026 |
| 2.3.1-alpha0 | 120 | 1/9/2026 |
| 2.3.0 | 1,208 | 1/8/2026 |
| 2.3.0-alpha1 | 124 | 1/8/2026 |
| 2.1.0 | 130 | 1/8/2026 |
| 2.1.0-preview | 131 | 1/8/2026 |
| 2.0.1 | 129 | 1/8/2026 |
| 2.0.0 | 165 | 1/8/2026 |
| 2.0.0-alpha1 | 121 | 1/8/2026 |
| 1.7.1 | 450 | 12/11/2025 |
| 1.6.8 | 461 | 12/9/2025 |
| 1.6.7 | 460 | 12/9/2025 |
1.0.0
- Initial release
- LoadImageAtom with file I/O tracking
- ResizeImageAtom with configurable sizes
- ExifProcessingAtom for metadata
- WatermarkAtom with alignment options
- ImagePipeline fluent builder
- Full signal emission for observability
- Production-ready resource management
- ImageSharpCancellationHook for signal-based cancellation
- Clean cancellation via 'imagesharp.stop' signal
- Automatic resource cleanup on cancellation