Skip to content

ImageSpace

Photo, Video & Motionv0.3.2-alpha.1 MIT 2

A local-first layered image studio in C#, on desktop and the web

ImageSpace is an independent layered image editor built with Uno Platform, SkiaSharp and an optional WebGPU compute backend. Its photo-editing workspace runs as a genuine Uno WebAssembly application and shares its C# editor with Windows, Linux and macOS hosts. Eleven reusable .NET libraries separate the tile-based document model, brushes, selections, filters, transactions, file formats, rendering, controls, viewport and workbench.

11reusable .NET libraries
16layer blend modes
14CPU filters
8optional WebGPU color kernels
ImageSpacewieslawsoltes.github.io/ImageSpace/
ImageSpace running in a browser
Highlights

What makes ImageSpace special

Familiar if you know Photoshop-style image editors — but original, local‑first and written end‑to‑end in C#.

Genuine Uno WebAssembly

The browser workspace is a real Uno app sharing its C# editor with desktop hosts — not a monolithic canvas or an HTML mockup.

Layers, type and shapes

Sparse pixel layers, editable multiline type and rectangle/ellipse shapes with sixteen blend modes, opacity, locks and reordering.

Pressure-aware painting

Brush, pencil, eraser and clone with size, hardness, opacity and flow, plus basic smudge, dodge and burn.

Non-destructive Curves & Levels

Adjustment layers with composite RGB and per-channel graphs, numeric and keyboard editing, presets and live undoable previews.

Independent masks

Pixel, type, shape and adjustment masks with density, feather, unlinked affine placement and a separate Apply layer mask workflow.

Rich selections

Rectangle, ellipse, lasso and contiguous color with add/subtract/intersect, feather, expand, contract, border and smooth.

Optional WebGPU compute

A reusable browser WebGPU module accelerates eight color kernels, with an explicit CPU fallback and the tile model as source of truth.

Copy-on-write tiles

Sparse 128-pixel RGBA tiles with copy-on-write snapshots power transactional undo without copying every pixel per stroke.

0reusable libraries
0lines of C# & XAML
0features catalogued
0.NET projects
0CI workflows
0docs
Feature tour

29 capabilities, 6 areas

Paint

  • Pressure-aware brush, pencil and eraser with size, hardness, opacity and flow
  • Clone from an Alt-click source on the active layer
  • Basic smudge, dodge and burn
  • Fill and linear gradient on the active pixel layer
  • Color spectrum, hex dialog and foreground/background swatches

Selection

  • Rectangle, ellipse, lasso and contiguous color selection
  • Add, subtract and intersect with Shift, Alt and Shift+Alt
  • Invert, feather and alpha selection
  • Expand, contract, border and smooth that preserve soft coverage

Layers and masks

  • Pixel, editable multiline type and rectangle/ellipse shape layers
  • Sixteen blend modes, opacity, visibility, locks, duplicate, reorder and rasterize
  • Masks on pixel, type, shape and adjustment layers with density and feather
  • Unlink masks to move, rotate and resize coverage; relink without a jump
  • Grayscale and red-overlay mask inspection views
  • Apply layer mask bakes coverage into pixel alpha as one undoable edit

Tone and effects

  • Curves with 2–16 points per channel and shape-preserving cubic interpolation
  • Levels with input black/white, gamma and output endpoints
  • Live adjustments: brightness/contrast, saturation, invert, grayscale, sepia, blur
  • Fourteen CPU color/convolution filters that respect selections
  • Eight optional WebGPU kernels with CPU fallback

Geometry and navigation

  • Eight-handle scaling, rotation and constrained motion
  • Numeric X/Y/W/H/Angle properties
  • Non-destructive crop with thirds guides that preserves off-canvas pixels
  • Canvas size and image size; wheel zoom, Space to pan, rulers and pixel grid

Files and recovery

  • Editable .imagespace archives (reader accepts versions 1–4)
  • Bounded PSD v1 RGB/8 import and export, including placed user masks
  • PNG, JPEG and WebP import and composite export
  • Up to twelve document tabs
  • Local IndexedDB or atomic desktop recovery of the active document
Integrated playground

Run ImageSpace right here

Open the live WebAssembly workspace and edit the original After the light sample — pixel, type and shape layers are ready to explore. Switch viewports, go fullscreen and tick off the guided tour as you explore.

https://wieslawsoltes.github.io/ImageSpace/
ImageSpace screenshot

Try ImageSpace live

Open the live WebAssembly workspace and edit the original After the light sample — pixel, type and shape layers are ready to explore.

Real C# · Uno WebAssembly · runs locally in your browser
Booting .NET WebAssembly runtime…
Reusable packages

11 libraries you can embed

ImageSpace is assembled from independently reusable libraries, each published on NuGet.org. Hover the graph to trace dependencies; click a node to jump to its package, and open a card for install and usage.

Core 1,015 lines Imaging 978 lines Filters 219 lines Editing 449 lines Documents 1,046 lines Skia 692 lines WebGpu 14 lines Storage 11 lines Controls 871 lines Editor 835 lines Workbench 3,094 lines App 236 lines
Arrows point from a project to what it references. Bars show relative size. ModelEngineI/ORenderingServiceControlsEditorWorkbench
Model 0.3.2-alpha.1

ImageSpace.Core

Colors, copy-on-write tiles, layers, document invariants and tone curve/levels math

No UI depsnet10.0
1,015 lines14 files0 refsUsage
Engine 0.3.2-alpha.1

ImageSpace.Imaging

Pressure-aware brushes, selections, flood fill, raster geometry and histograms

No UI depsnet10.0
refs → Core
978 lines10 files1 refsUsage
Engine 0.3.2-alpha.1

ImageSpace.Filters

Selection-aware color/convolution filters and sparse tone-lookup processing

No UI depsnet10.0
refs → Core
219 lines2 files1 refsUsage
Engine 0.3.2-alpha.1

ImageSpace.Editing

Transactions, bounded tile history, rollback, mask application and editor commands

No UI depsnet10.0
refs → Imaging, Filters
449 lines7 files2 refsUsage
I/O 0.3.2-alpha.1

ImageSpace.Documents

Versioned native archives and bounded 8-bit RGB PSD codec

No UI depsnet10.0
refs → Core
1,046 lines7 files1 refsUsage
Rendering 0.3.2-alpha.1

ImageSpace.Skia

Compositor, revision-cached tile images, live adjustments, mask baking and image codecs

SkiaSharpnet10.0
refs → Core
nuget → SkiaSharp
692 lines6 files1 refsUsage
Service 0.3.2-alpha.1

ImageSpace.WebGpu

WebGPU compute contracts and standalone JavaScript/WGSL RGBA kernels

WebGPUnet10.0
refs → Filters
14 lines1 files1 refsUsage
Service 0.3.2-alpha.1

ImageSpace.Storage

Host-independent file and recovery contracts

No UI depsnet10.0
11 lines1 files0 refsUsage
Controls 0.3.2-alpha.1

ImageSpace.Controls

Custom icons, buttons, menus, numeric fields, spectrum, histograms, curve and levels editors

Uno + Skianet10.0-browserwasmnet10.0-desktop
refs → Core
871 lines8 files1 refsUsage
Editor 0.3.2-alpha.1

ImageSpace.Editor

Embeddable viewport with camera, paint, selection, transform and crop gestures

Uno + Skianet10.0-browserwasmnet10.0-desktop
refs → Editing, Skia, Controls
835 lines7 files3 refsUsage
Workbench 0.3.2-alpha.1

ImageSpace.Workbench

Workspace, panels, tonal inspector, menus, commands and multi-document tabs

Uno + Skianet10.0-browserwasmnet10.0-desktop
refs → Editor, Documents, Storage
3,094 lines22 files3 refsUsage
Install from NuGet

11 packages on NuGet.org

All eleven libraries are MIT-licensed and published on NuGet.org. The first eight (Core, Storage, Imaging, Filters, WebGpu, Editing, Documents, Skia) target net10.0 and have no Uno dependency; only ImageSpace.Skia needs SkiaSharp. ImageSpace.Controls, ImageSpace.Editor and ImageSpace.Workbench target net10.0-desktop and net10.0-browserwasm on Uno Platform with the Skia renderer. All packages are versioned together (currently prereleases), and symbols ship on NuGet.org as .snupkg with SourceLink.

11packages
0.3.2-alpha.1pre-release · Sep 29, 2026
8plain .NET — no UI framework
Newjust published
bashStart with the foundation package
dotnet add package ImageSpace.Core --prerelease
xmlEvery package — keep only what you need
<PackageReference Include="ImageSpace.Core" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Storage" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Imaging" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Filters" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.WebGpu" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Editing" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Documents" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Skia" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Controls" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Editor" Version="0.3.2-alpha.1" />
<PackageReference Include="ImageSpace.Workbench" Version="0.3.2-alpha.1" />
xmlCentral package management
<!-- Directory.Packages.props -->
<ItemGroup>
  <PackageVersion Include="ImageSpace.Core" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Storage" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Imaging" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Filters" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.WebGpu" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Editing" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Documents" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Skia" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Controls" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Editor" Version="0.3.2-alpha.1" />
  <PackageVersion Include="ImageSpace.Workbench" Version="0.3.2-alpha.1" />
</ItemGroup>

Published with NuGet Trusted Publishing from GitHub Actions, with symbols (.snupkg) and SourceLink. Release v0.3.2-alpha.1 ↗

Dependencies: Core ← Imaging, Filters, Documents, Skia, Controls; Filters ← WebGpu; Imaging + Filters ← Editing; Editing + Skia + Controls ← Editor; Editor + Documents + Storage ← Workbench. Storage has no dependencies. CI also produces the standalone npm tarball @wieslawsoltes/imagespace-webgpu (from packages/webgpu); it is not a NuGet package, and its npm publication is separate.

ImageSpace.Core

ModelNo UI depsnet10.0v0.3.2-alpha.1

The document model shared by every other package: ImageDocument with raster, text, shape and adjustment Layers, sparse copy-on-write PixelSurface tiles (128×128, straight-alpha RGBA), masks with affine placement, and immutable Curves/Levels settings with lookup-table math. Use it standalone for headless image generation or processing. No dependencies and no UI.

bashInstall
dotnet add package ImageSpace.Core --prerelease
Depends onno other ImageSpace packageUsed byImagingFiltersDocumentsSkiaControls

Key types

  • ImageDocument: size, DPI, ordered Layers, ActiveLayerId, optional Selection coverage, Snapshot() and Validate().
  • Layer: kind, blend, opacity, transform, Pixels, Mask and mask placement, Curves/Levels; Layer.Raster(...) factory.
  • PixelSurface: tiled pixels with Get/Set/Fill, row copies, FromRgba/ToRgba and per-tile revisions (limit 8192 px per side, 16 MP).
  • Rgba32: 8-bit sRGB color with Parse, Lerp and Over.
  • ToneCurve, CurvesAdjustment, LevelsChannel, LevelsAdjustment: immutable, validated tone settings.
  • RgbLookupTables: FromCurves/FromLevels (with strength) and Map(Rgba32).

Basic usage

csharpUsage
using ImageSpace.Core;

var document = new ImageDocument(1600, 1000, "Poster");
var paint = Layer.Raster("Paint", document.Width, document.Height);
paint.Pixels!.Fill(Rgba32.Parse("#1E2A3A"));
paint.Pixels.Set(10, 10, new Rgba32(255, 128, 0));
document.Layers.Add(paint);
document.ActiveLayerId = paint.Id;
document.Validate();

// Pure tone math: immutable settings become 8-bit lookup tables.
var curves = new CurvesAdjustment
{
    Rgb = new ToneCurve { Points = [new(0, 0), new(64, 38), new(192, 218), new(255, 255)] }
};
Rgba32 mapped = RgbLookupTables.FromCurves(curves).Map(paint.Pixels.Get(10, 10));

See the Curves/Levels reuse guide for interpolation and compatibility details.

ImageSpace.Storage

ServiceNo UI depsnet10.0v0.3.2-alpha.1

The contract a host implements so the workbench can open, save and recover files without knowing about browsers, pickers or file systems. No dependencies and no UI.

bashInstall
dotnet add package ImageSpace.Storage --prerelease
Depends onno other ImageSpace packageUsed byWorkbench

Key types

  • IEditorStorage: OpenAsync, SaveAsync (return false on cancel), ReadRecoveryAsync, WriteRecoveryAsync, ClearRecoveryAsync.
  • OpenedFile(Name, Bytes): a file chosen by the user.

Basic usage

csharpUsage
using ImageSpace.Storage;

sealed class FolderStorage(string folder) : IEditorStorage
{
    private string Recovery => Path.Combine(folder, "recovery.imagespace");

    public Task<OpenedFile?> OpenAsync(CancellationToken cancellationToken = default) =>
        Task.FromResult<OpenedFile?>(null); // show a host file picker here

    public async Task<bool> SaveAsync(string name, byte[] bytes, string contentType, CancellationToken cancellationToken = default)
    {
        await File.WriteAllBytesAsync(Path.Combine(folder, name), bytes, cancellationToken);
        return true; // return false when the user cancels
    }

    public async Task<byte[]?> ReadRecoveryAsync(CancellationToken cancellationToken = default) =>
        File.Exists(Recovery) ? await File.ReadAllBytesAsync(Recovery, cancellationToken) : null;

    public Task WriteRecoveryAsync(byte[] bytes, CancellationToken cancellationToken = default) =>
        File.WriteAllBytesAsync(Recovery, bytes, cancellationToken);

    public Task ClearRecoveryAsync(CancellationToken cancellationToken = default)
    {
        File.Delete(Recovery);
        return Task.CompletedTask;
    }
}

ImageSpace.Imaging

EngineNo UI depsnet10.0v0.3.2-alpha.1

Raster editing primitives: a pressure-aware brush engine (brush, pencil, eraser, clone, dodge, burn, smudge), rectangle/ellipse/polygon/contiguous selections with add/subtract/intersect, O(n) selection morphology, fills and gradients that honor selection coverage, histograms, resize/crop/rotate/flip and the original sample document. Depends on Core; no UI.

bashInstall
dotnet add package ImageSpace.Imaging --prerelease
Depends onCoreUsed byEditing

Key types

  • BrushEngine with BrushSettings(Size, Hardness, Opacity, Flow, Spacing, Pressure) and PaintMode.
  • Selections: Rectangle, Polygon, Contiguous, Combine, Invert, Bounds.
  • SelectionMorphology.Apply(...): expand, contract, border and smooth; SelectionContours.Trace for exact outlines.
  • PixelEdits: Fill, Gradient, Clear; MaskOperations for mask/selection conversion.
  • RasterOperations: Resize, Crop, Rotate90, Flip, Histogram, Sample.

Basic usage

csharpUsage
using System.Numerics;
using ImageSpace.Core;
using ImageSpace.Imaging;

var document = new ImageDocument(800, 600);
var layer = Layer.Raster("Paint", 800, 600);
document.Layers.Add(layer);
document.ActiveLayerId = layer.Id;

// Edits respect the document selection's coverage.
var ellipse = Selections.Rectangle(800, 600, new Vector2(100, 100), new Vector2(500, 400), ellipse: true);
document.Selection = SelectionMorphology.Apply(ellipse, SelectionModification.Smooth, radius: 4);
PixelEdits.Gradient(document, layer, new(100, 100), new(500, 400), Rgba32.Parse("#FF8A3D"), Rgba32.Parse("#3D7BFF"));

var brush = new BrushEngine();
brush.Begin(layer.Pixels!, Vector2.Zero);
foreach (var x in new[] { 150f, 250f, 350f })
    brush.Paint(document, layer, new Vector2(x, 250), 0.8f, new BrushSettings(Size: 24), Rgba32.White, PaintMode.Brush);
brush.End();

int[] histogram = RasterOperations.Histogram(layer.Pixels!);

ImageSpace.Filters

EngineNo UI depsnet10.0v0.3.2-alpha.1

Deterministic, allocation-conscious pixel filters on PixelSurface: invert, grayscale, sepia, brightness/contrast, saturation, gamma, threshold, posterize, Gaussian blur, sharpen, emboss, edges, pixelate and noise, plus sparse tone-lookup processing for Curves/Levels. Source surfaces are never modified. Depends on Core; no UI.

bashInstall
dotnet add package ImageSpace.Filters --prerelease
Depends onCoreUsed byWebGpuEditing

Key types

  • FilterEngine.Apply(source, kind, amount, secondary, seed) and FilterEngine.Blur(source, sigma).
  • FilterKind: the fourteen supported kernels.
  • ToneFilterEngine.Apply(source, RgbLookupTables, cancellationToken): cancellable lookup-table processing that preserves alpha.

Basic usage

csharpUsage
using ImageSpace.Core;
using ImageSpace.Filters;

PixelSurface blurred = FilterEngine.Blur(source, sigma: 3);
PixelSurface sepia = FilterEngine.Apply(source, FilterKind.Sepia);
PixelSurface punchy = FilterEngine.Apply(source, FilterKind.BrightnessContrast, amount: 10, secondary: 20);

var levels = new LevelsAdjustment
{
    Rgb = new LevelsChannel { InputBlack = 16, InputWhite = 240, Gamma = 1.2 },
    Blue = new LevelsChannel { Gamma = 0.95 }
};
PixelSurface toned = ToneFilterEngine.Apply(source, RgbLookupTables.FromLevels(levels), cancellationToken);

ImageSpace.WebGpu

ServiceWebGPUnet10.0v0.3.2-alpha.1

Contracts for optional GPU compute plus the standalone WebGPU JavaScript/WGSL kernels (WebGpu.js, shipped as an embedded resource and as contentFiles/any/any/ImageSpace/WebGpu.js). Eight color kernels are accelerated (invert, grayscale, sepia, brightness/contrast, saturation, gamma, threshold, posterize); everything else, and any unavailable adapter, falls back to the CPU. Depends on Filters; no UI. The host (for example a browser interop layer) implements IComputeFilterBackend.

bashInstall
dotnet add package ImageSpace.WebGpu --prerelease
Depends onFilters

Key types

  • IComputeFilterBackend: GetCapabilitiesAsync and ApplyAsync (returns null for unsupported kernels; never mutates the source).
  • GpuCapabilities(Available, Backend, MaximumBufferSize, Reason).
  • GpuKernels.Supports(FilterKind): which kernels have a GPU implementation.

Basic usage

csharpUsage
using ImageSpace.Core;
using ImageSpace.Filters;
using ImageSpace.WebGpu;

static async Task<PixelSurface> ApplyAsync(IComputeFilterBackend gpu, PixelSurface source, FilterKind kind, float amount)
{
    if (GpuKernels.Supports(kind))
    {
        GpuCapabilities caps = await gpu.GetCapabilitiesAsync();
        if (caps.Available && await gpu.ApplyAsync(source, kind, amount, 0) is { } output)
            return output;
    }
    return FilterEngine.Apply(source, kind, amount); // deterministic CPU fallback
}

ImageSpace.Editing

EngineNo UI depsnet10.0v0.3.2-alpha.1

The transactional EditorSession: every edit runs against a snapshot, commits as one history entry or rolls back on failure, and undo/redo stays within a memory budget. It also hosts layer, mask, selection, filter and canvas commands. Depends on Imaging and Filters; no UI.

bashInstall
dotnet add package ImageSpace.Editing --prerelease
Depends onImagingFiltersUsed byEditor

Key types

  • EditorSession: Document, Execute(name, action), Begin/Commit/Cancel, Undo/Redo, History, HistoryBudget, Changed.
  • Layer and canvas commands: AddLayer, DuplicateLayer, MoveLayer, AddMask, ApplyFilter, Crop, ResizeImage, RotateCanvas.
  • Mask commands: SetMaskProperties, SetMaskLinked, InvertMask, ApplyLayerMask / CanApplyLayerMask.
  • PixelFilterPipeline and HistoryEntry.

Basic usage

csharpUsage
using ImageSpace.Core;
using ImageSpace.Editing;
using ImageSpace.Filters;

var session = new EditorSession(new ImageDocument(1600, 1000, "Untitled"));
session.Changed += (_, _) => Console.WriteLine($"Revision {session.Revision}");

session.AddLayer("Paint");                             // one history entry
session.FillPixels(new Rgba32(54, 145, 230));
session.ApplyFilter(FilterKind.GaussianBlur, amount: 4);

session.Execute("Add Curves", document =>              // arbitrary edit, one undo step
{
    var adjustment = new Layer
    {
        Name = "Contrast", Kind = LayerKind.Adjustment, Adjustment = AdjustmentKind.Curves,
        Width = document.Width, Height = document.Height,
        Curves = new CurvesAdjustment { Rgb = new ToneCurve { Points = [new(0, 0), new(64, 38), new(192, 218), new(255, 255)] } }
    };
    document.Layers.Add(adjustment);
    document.ActiveLayerId = adjustment.Id;
});
session.Undo();

ImageSpace.Documents

I/ONo UI depsnet10.0v0.3.2-alpha.1

File formats: the versioned native .imagespace archive (manifest versions 1–4, minimal version written) for lossless roundtrips, and bounded RGB/8 PSD import/export (raw, PackBits, ZIP and predicted ZIP channels, Unicode names, resolution and user masks). Archive size, dimensions and layer counts are bounded. Depends on Core; no UI.

bashInstall
dotnet add package ImageSpace.Documents --prerelease
Depends onCoreUsed byWorkbench

Key types

  • DocumentArchive.Save(ImageDocument) / DocumentArchive.Load(ReadOnlyMemory<byte>).
  • PsdCodec.Load(bytes, name): returns PsdCodec.ImportResult(Document, Warnings).
  • PsdCodec.Save(document, rasterize, composite, compression) with PsdCompression.

Basic usage

csharpUsage
using ImageSpace.Core;
using ImageSpace.Documents;
using ImageSpace.Skia;

PsdCodec.ImportResult imported = PsdCodec.Load(File.ReadAllBytes("artwork.psd"), "artwork");
foreach (var warning in imported.Warnings)
    Console.WriteLine(warning);

// Lossless native archive (.imagespace) roundtrip.
byte[] archive = DocumentArchive.Save(imported.Document);
ImageDocument reopened = DocumentArchive.Load(archive);

// PSD export needs rasterized layers and a composite, e.g. from ImageSpace.Skia.
using var renderer = new ImageRenderer();
byte[] psd = PsdCodec.Save(reopened, layer => renderer.RasterizeLayer(reopened, layer),
    renderer.Rasterize(reopened), PsdCompression.Zip);

PSD is bounded raster interchange, not lossless Photoshop editing; see Files and limits.

ImageSpace.Skia

RenderingSkiaSharpnet10.0v0.3.2-alpha.1

The shared compositor: draws revision-cached tiles, text, shapes, sixteen blend modes, masks and live adjustments (including Curves/Levels lookup filters) into any SKCanvas, rasterizes and exports PNG/JPEG/WebP, decodes images and bakes masks. The host picks the Skia backend (GPU or software). Depends on Core and SkiaSharp 3.119; no UI framework.

bashInstall
dotnet add package ImageSpace.Skia --prerelease
Depends onCoreUsed byEditor

Key types

  • ImageRenderer: Draw, DrawLayer, Rasterize, RasterizePreview, RasterizeLayer, Export, Decode, SetTypeface, BakeLayerMask.
  • MaskPreviewRenderer with MaskPreviewMode (composite, grayscale, red overlay).
  • DocumentPreviewCache: bounded, reusable document previews for thumbnails and histograms.

Basic usage

csharpUsage
using ImageSpace.Core;
using ImageSpace.Skia;
using SkiaSharp;

using var renderer = new ImageRenderer();

// Composite into any SKCanvas (an Uno SKCanvasElement, an SKSurface, ...).
using var surface = SKSurface.Create(new SKImageInfo(document.Width, document.Height));
renderer.Draw(surface.Canvas, document);

File.WriteAllBytes("poster.webp", renderer.Export(document, SKEncodedImageFormat.Webp, quality: 90));
PixelSurface preview = renderer.RasterizePreview(document, maximumEdge: 256);
PixelSurface decoded = ImageRenderer.Decode(File.ReadAllBytes("photo.jpg"));

Use a renderer on its owning thread. To apply a layer mask destructively, pass the renderer to the session: if (session.CanApplyLayerMask) session.ApplyLayerMask(renderer.BakeLayerMask);. See the rendering guide for ownership rules.

ImageSpace.Controls

ControlsUno + Skianet10.0-browserwasmnet10.0-desktopv0.3.2-alpha.1

Self-contained, dark-themed Uno controls used by the workbench and reusable without it: Skia-drawn icons, buttons and menus, numeric fields, an HSV color spectrum, histograms, and the Curves/Levels graph editors with begin/change/complete/cancel semantics. Depends on Core; requires Uno Platform (Skia renderer).

bashInstall
dotnet add package ImageSpace.Controls --prerelease
Depends onCoreUsed byEditor

Key types

  • CurveEditor: Curve (ToneCurve), CurveChanged, EditCompleted, EditCanceled, SetHistogram, TryBeginEdit.
  • LevelsControl: Value (LevelsChannel), ValueChanged, EditCompleted, EditCanceled, SetHistogram.
  • NumericField(label, value, minimum, maximum), ColorSpectrum (ColorChanged), HistogramView (Values).
  • StudioButton(text, action, icon), IconView(icon), Studio (theme brushes, Font, Menu), AdaptivePanelGrid.

Basic usage

csharpUsage
using ImageSpace.Controls;
using ImageSpace.Core;
using Microsoft.UI.Xaml.Controls;

var curve = new CurveEditor { Curve = new ToneCurve { Points = [new(0, 0), new(128, 150), new(255, 255)] } };
curve.CurveChanged += value => ShowPreview(value);     // live, uncommitted
curve.EditCompleted += () => Commit(curve.Curve);
curve.EditCanceled += RevertPreview;

var levels = new LevelsControl { Value = new LevelsChannel { InputBlack = 12, Gamma = 1.1 } };
levels.ValueChanged += channel => ShowPreview(channel);

var opacity = new NumericField("Opacity", 100, 0, 100);
opacity.ValueChanged += value => SetOpacity((float)(value / 100));

window.Content = new StackPanel
{
    Spacing = 8,
    Children = { curve, levels, opacity, new StudioButton("Reset", ResetAll, icon: "undo") }
};

ImageSpace.Editor

EditorUno + Skianet10.0-browserwasmnet10.0-desktopv0.3.2-alpha.1

ImageViewport, the embeddable canvas: Skia compositing with a separate overlay layer, rulers, grid, transform handles, pan/zoom, and pointer gestures for every tool (move, marquee, lasso, wand, crop, brushes, gradient, fill, text, shapes, eyedropper). Each gesture becomes one EditorSession transaction. Depends on Editing, Skia and Controls; requires Uno Platform.

bashInstall
dotnet add package ImageSpace.Editor --prerelease

Key types

  • ImageViewport(EditorSession): Tool, Brush, Foreground, BackgroundColor, Tolerance, ShowRulers, ShowGrid, Renderer.
  • Navigation: Fit, SetZoom, Zoom, Pan, ToDocument/ToScreen.
  • Events: Status, ColorPicked, TextEditRequested, ViewChanged; SetMaskPreview(MaskPreviewMode).
  • EditorTool: the 21 tools.

Basic usage

csharpUsage
using ImageSpace.Core;
using ImageSpace.Editing;
using ImageSpace.Editor;
using ImageSpace.Imaging;

var session = new EditorSession(new ImageDocument(1600, 1000, "Untitled"));
session.AddLayer("Paint");
var viewport = new ImageViewport(session)
{
    Tool = EditorTool.Brush,
    Brush = new BrushSettings(Size: 24, Hardness: 0.9f),
    Foreground = new Rgba32(54, 145, 230)
};
viewport.ColorPicked += color => viewport.Foreground = color;
window.Content = viewport;

Dispose the viewport when its window closes.

ImageSpace.Workbench

WorkbenchUno + Skianet10.0-browserwasmnet10.0-desktopv0.3.2-alpha.1

The complete ImageSpace workspace as one control: menus, tool bar and options, layers/channels/history/properties panels, the tonal inspector, mask editing, multi-document tabs, filter dialogs and open/save/export commands through IEditorStorage. Depends on Editor, Documents and Storage; requires Uno Platform.

bashInstall
dotnet add package ImageSpace.Workbench --prerelease

Key types

  • StudioWorkbench(EditorSession, IEditorStorage): Session, Surface (the ImageViewport), Documents, AddDocument, SelectTool, OpenAsync, SaveAsync, ShowStatus.
  • StudioWorkbench.GpuFilter: optional GPU filter delegate (for example an IComputeFilterBackend.ApplyAsync); CPU kernels are used otherwise.
  • Reusable panels: LayerPanel, PropertyPanel, ToneAdjustmentEditor, MaskPropertiesEditor.

Basic usage

csharpUsage
using ImageSpace.Documents;
using ImageSpace.Editing;
using ImageSpace.Imaging;
using ImageSpace.Storage;
using ImageSpace.Workbench;

IEditorStorage storage = new FolderStorage(dataFolder); // see ImageSpace.Storage
var document = await storage.ReadRecoveryAsync() is { } saved
    ? DocumentArchive.Load(saved)
    : SampleDocument.Create();

var workbench = new StudioWorkbench(new EditorSession(document), storage);
window.Content = workbench;
window.Closed += (_, _) => workbench.Dispose();
Architecture

From engine to workbench

Layers build bottom‑up: each one depends only on those beneath it.

L1Core model

Straight-alpha RGBA8 pixels in sparse 128×128 copy-on-write tiles, layers and tone math.

ImageSpace.Core
L2Raster engines and files

Brushes, selections and filters operate on the tile model; native archives and PSD are read and written here.

ImageSpace.ImagingImageSpace.FiltersImageSpace.Documents
L3Transactions and rendering

Transactional history over tiles, a revision-cached Skia compositor and an optional parallel WebGPU compute service.

ImageSpace.EditingImageSpace.SkiaImageSpace.WebGpu
L4Uno controls and viewport

Custom controls and an embeddable viewport drawing into Uno's SKCanvasElement.

ImageSpace.ControlsImageSpace.Editor
L5Workbench

Panels, menus and multi-document workflows, with hosts injecting file and recovery storage.

ImageSpace.WorkbenchImageSpace.Storage
Use it in code

Embed, script, extend

csharpEmbed the viewport
using ImageSpace.Core;
using ImageSpace.Editing;
using ImageSpace.Editor;

var session = new EditorSession(new ImageDocument(1600, 1000, "Untitled"));
session.AddLayer("Paint");
window.Content = new ImageViewport(session)
{
    Tool = EditorTool.Brush,
    Foreground = new Rgba32(54, 145, 230)
};
bashBuild and run
dotnet workload install wasm-tools --skip-manifest-update
python3 scripts/fetch-assets.py

# Native desktop host.
dotnet run --project src/ImageSpace.App -f net10.0-desktop -p:ImageSpaceDesktopOnly=true

# Genuine Uno browser application, with the GitHub Pages project prefix.
dotnet publish src/ImageSpace.App -c Release -f net10.0-browserwasm \
  -p:WasmShellWebAppBasePath=/ImageSpace/ -o artifacts/publish
python3 scripts/collect-site.py artifacts/publish site
python3 scripts/serve-site.py --root site --port 4173

File formats

.imagespaceread/write
.psd (v1 RGB/8)read/write
.pngread/write
.jpg / .jpegread/write
.webpread/write
.bmpimport
.gif (static)import

Keyboard essentials

New / Open / Save native archiveCtrl N / Ctrl O / Ctrl S
Undo / redoCtrl Z / Ctrl Shift Z
New pixel layer / duplicateCtrl Shift N / Ctrl J
Select all / deselect / inverseCtrl A / Ctrl D / Ctrl Shift I
Invert pixels or active maskCtrl I
Curves / Levels adjustmentCtrl M / Ctrl L
Brush / eraser / cloneB / E / S
Rectangle / ellipse / lasso / color selectM / Shift M / L / W
Move / crop / textV / C / T
Brush size[ / ]
Fit / 100% / rulersCtrl 0 / Ctrl 1 / Ctrl R
Swap colors / reset black and whiteX / D
Build & CI

Continuously verified

Each push runs through GitHub Actions. The public browser build is deployed only from successful pipelines.

Toolchain

.NET SDK10.0.401
Uno SDK6.7.30
Target frameworksnet10.0-browserwasm · net10.0-desktop · net10.0

Third‑party NuGet packages

SkiaSharp3.119.4
bashClone & run the browser build locally
git clone https://github.com/wieslawsoltes/ImageSpace.git
cd ImageSpace
dotnet workload install wasm-tools
dotnet run --project src/ImageSpace.App -f net10.0-browserwasm
Honest boundaries

What it doesn’t claim

The project documents its limits as carefully as its features.

  • Follows familiar Photoshop conventions but is not pixel-identical or at full Photoshop parity
  • PSD is bounded RGB/8 raster interchange: no smart objects, groups, clipping chains or ICC profiles
  • No layer groups, pen/path editor, layer styles, healing/content-aware tools or Camera Raw
  • Working limits: 8192 px per side, 16 megapixels per surface, 128 layers and twelve documents
  • WebGPU accelerates eight color kernels only; it is not a GPU-only engine