Browse Source

Merge pull request #696 from PixiEditor/gamma-correction

Gamma correction
Krzysztof Krysiński 8 months ago
parent
commit
445bd68747
58 changed files with 598 additions and 307 deletions
  1. 5 4
      src/ChunkyImageLib/Chunk.cs
  2. 13 11
      src/ChunkyImageLib/ChunkyImage.cs
  3. 1 1
      src/ChunkyImageLib/CommittedChunkStorage.cs
  4. 2 0
      src/ChunkyImageLib/IReadOnlyChunkyImage.cs
  5. 1 1
      src/Drawie
  6. 5 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/ProcessingColorSpace_ChangeInfo.cs
  7. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  8. 3 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IChunkRenderable.cs
  9. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IPreviewRenderable.cs
  10. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs
  11. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs
  12. 14 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  13. 1 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineChannelsNode.cs
  14. 2 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateChannelsNode.cs
  15. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  16. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/DebugBlendModeNode.cs
  17. 1 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs
  18. 5 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  19. 9 8
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  20. 1 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  21. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageLeftNode.cs
  22. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageRightNode.cs
  23. 16 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  24. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NoiseNode.cs
  25. 1 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  26. 2 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs
  27. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RasterizeShapeNode.cs
  28. 0 14
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/ShapeNode.cs
  29. 7 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  30. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs
  31. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  32. 7 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs
  33. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  34. 37 0
      src/PixiEditor.ChangeableDocument/Changes/Properties/ChangeProcessingColorSpace_Change.cs
  35. 5 6
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  36. 5 1
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  37. 256 0
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.deps.json
  38. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  39. 3 1
      src/PixiEditor/Data/Localization/Languages/en.json
  40. 1 1
      src/PixiEditor/Helpers/Behaviours/TextBlockExtensions.cs
  41. 7 0
      src/PixiEditor/Helpers/DocumentViewModelBuilder.cs
  42. 1 0
      src/PixiEditor/Helpers/Extensions/PixiParserDocumentEx.cs
  43. 8 0
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  44. 6 0
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  45. 2 0
      src/PixiEditor/Models/Handlers/IDocument.cs
  46. 4 0
      src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs
  47. 3 2
      src/PixiEditor/Models/Rendering/AnimationPreviewRenderer.cs
  48. 54 40
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  49. 13 3
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  50. 5 8
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  51. 13 0
      src/PixiEditor/ViewModels/Document/DocumentManagerViewModel.cs
  52. 1 0
      src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs
  53. 31 75
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  54. 26 79
      src/PixiEditor/Views/Rendering/Scene.cs
  55. 1 2
      src/PixiEditor/Views/Visuals/PreviewPainterControl.cs
  56. 1 1
      src/PixiParser
  57. 2 1
      tests/ChunkyImageLibTest/ChunkyImageTests.cs
  58. 1 0
      tests/PixiEditor.Backend.Tests/MockDocument.cs

+ 5 - 4
src/ChunkyImageLib/Chunk.cs

@@ -2,6 +2,7 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 
@@ -48,21 +49,21 @@ public class Chunk : IDisposable
     public bool Disposed => returned;
 
     private Surface internalSurface;
-    private Chunk(ChunkResolution resolution)
+    private Chunk(ChunkResolution resolution, ColorSpace colorSpace)
     {
         int size = resolution.PixelSize();
 
         Resolution = resolution;
         PixelSize = new(size, size);
-        internalSurface = new Surface(PixelSize);
+        internalSurface = new Surface(new ImageInfo(size, size, ColorType.RgbaF16, AlphaType.Premul, colorSpace));
     }
 
     /// <summary>
     /// Tries to take a chunk with the <paramref name="resolution"/> from the pool, or creates a new one
     /// </summary>
-    public static Chunk Create(ChunkResolution resolution = ChunkResolution.Full)
+    public static Chunk Create(ColorSpace chunkCs, ChunkResolution resolution = ChunkResolution.Full)
     {
-        var chunk = ChunkPool.Instance.Get(resolution) ?? new Chunk(resolution);
+        var chunk = ChunkPool.Instance.Get(resolution) ?? new Chunk(resolution, chunkCs);
         chunk.returned = false;
         Interlocked.Increment(ref chunkCounter);
         return chunk;

+ 13 - 11
src/ChunkyImageLib/ChunkyImage.cs

@@ -75,6 +75,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     private static Paint AddingPaint { get; } = new Paint() { BlendMode = BlendMode.Plus };
     private readonly Paint blendModePaint = new Paint() { BlendMode = BlendMode.Src };
 
+    public ColorSpace ProcessingColorSpace { get; set; } = ColorSpace.CreateSrgbLinear();
+    
     public int CommitCounter => commitCounter;
 
     public VecI CommittedSize { get; private set; }
@@ -324,7 +326,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                     : latestChunk.Surface.GetSRGBPixel(posInChunk);
                 // using a whole chunk just to draw 1 pixel is kinda dumb,
                 // but this should be faster than any approach that requires allocations
-                using Chunk tempChunk = Chunk.Create(ChunkResolution.Eighth);
+                using Chunk tempChunk = Chunk.Create(ProcessingColorSpace, ChunkResolution.Eighth);
                 using Paint committedPaint = new Paint() { Color = committedColor, BlendMode = BlendMode.Src };
                 using Paint latestPaint = new Paint() { Color = latestColor, BlendMode = this.blendMode };
                 tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, committedPaint);
@@ -381,7 +383,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             }
 
             // combine with committed and then draw
-            using var tempChunk = Chunk.Create(resolution);
+            using var tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
             tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0,
                 ReplacingPaint);
             blendModePaint.BlendMode = blendMode;
@@ -1022,7 +1024,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                     blendModePaint.BlendMode = blendMode;
                     if (lockTransparency)
                     {
-                        using Chunk tempChunk = Chunk.Create(resolution);
+                        using Chunk tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
                         tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(maybeCommitted.Surface.DrawingSurface, 0, 0,
                             ReplacingPaint);
                         maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0,
@@ -1197,7 +1199,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             return new FilledChunk();
         }
 
-        var intersection = Chunk.Create(resolution);
+        var intersection = Chunk.Create(ProcessingColorSpace, resolution);
         intersection.Surface.DrawingSurface.Canvas.Clear(Colors.White);
 
         foreach (var mask in activeClips)
@@ -1250,7 +1252,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             // drawing with raster clipping
             var clip = combinedRasterClips.AsT2;
 
-            using var tempChunk = Chunk.Create(targetChunk.Resolution);
+            using var tempChunk = Chunk.Create(ProcessingColorSpace, targetChunk.Resolution);
             targetChunk.DrawChunkOn(tempChunk.Surface.DrawingSurface, VecI.Zero, ReplacingPaint);
 
             CallDrawWithClip(chunkOperation, operationAffectedArea.GlobalArea, tempChunk, resolution, chunkPos);
@@ -1365,7 +1367,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         // for full res chunks: nothing exists, create brand new chunk
         if (resolution == ChunkResolution.Full)
         {
-            var newChunk = Chunk.Create(resolution);
+            var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
@@ -1374,7 +1376,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         Chunk? existingFullResChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
         if (existingFullResChunk is not null)
         {
-            var newChunk = Chunk.Create(resolution);
+            var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             newChunk.Surface.DrawingSurface.Canvas.Save();
             newChunk.Surface.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
 
@@ -1388,7 +1390,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         // for low res chunks: full res version doesn't exist
         {
             GetOrCreateCommittedChunk(chunkPos, ChunkResolution.Full);
-            var newChunk = Chunk.Create(resolution);
+            var newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
@@ -1408,7 +1410,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         var maybeCommittedAnyRes = MaybeGetCommittedChunk(chunkPos, resolution);
         if (maybeCommittedAnyRes is not null)
         {
-            Chunk newChunk = Chunk.Create(resolution);
+            Chunk newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             if (blendMode == BlendMode.Src)
                 maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
             else
@@ -1424,14 +1426,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
             //create low res committed chunk
             var committedChunkLowRes = GetOrCreateCommittedChunk(chunkPos, resolution);
             //create latest based on it
-            Chunk newChunk = Chunk.Create(resolution);
+            Chunk newChunk = Chunk.Create(ProcessingColorSpace, resolution);
             committedChunkLowRes.Surface.CopyTo(newChunk.Surface);
             latestChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
 
         // no previous chunks exist
-        var newLatestChunk = Chunk.Create(resolution);
+        var newLatestChunk = Chunk.Create(ProcessingColorSpace, resolution);
         newLatestChunk.Surface.DrawingSurface.Canvas.Clear();
         latestChunks[resolution][chunkPos] = newLatestChunk;
         return newLatestChunk;

+ 1 - 1
src/ChunkyImageLib/CommittedChunkStorage.cs

@@ -16,7 +16,7 @@ public class CommittedChunkStorage : IDisposable
     {
         foreach (var chunkPos in committedChunksToSave)
         {
-            Chunk copy = Chunk.Create();
+            Chunk copy = Chunk.Create(image.ProcessingColorSpace);
             if (!image.DrawCommittedChunkOn(chunkPos, ChunkResolution.Full, copy.Surface.DrawingSurface, VecI.Zero, ReplacingPaint))
             {
                 copy.Dispose();

+ 2 - 0
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -2,6 +2,7 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 
@@ -22,4 +23,5 @@ public interface IReadOnlyChunkyImage
     HashSet<VecI> FindAllChunks();
     VecI CommittedSize { get; }
     VecI LatestSize { get; }
+    public ColorSpace ProcessingColorSpace { get; }
 }

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 0c1d72f544e73128235d0a0112cd571bbb44858f
+Subproject commit bfb22ae2b5f17e0f750c8106ec4677614c697428

+ 5 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Properties/ProcessingColorSpace_ChangeInfo.cs

@@ -0,0 +1,5 @@
+using Drawie.Backend.Core.Surfaces.ImageData;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Properties;
+
+public record ProcessingColorSpace_ChangeInfo(ColorSpace NewColorSpace) : IChangeInfo;

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -32,6 +32,7 @@ internal class Document : IChangeable, IReadOnlyDocument
 
     IReadOnlyReferenceLayer? IReadOnlyDocument.ReferenceLayer => ReferenceLayer;
     public DocumentRenderer Renderer { get; }
+    public ColorSpace ProcessingColorSpace { get; internal set; } = ColorSpace.CreateSrgbLinear();
 
     /// <summary>
     /// The default size for a new document

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IChunkRenderable.cs

@@ -1,9 +1,10 @@
-using PixiEditor.ChangeableDocument.Changeables.Animations;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 public interface IChunkRenderable
 {
-    public void RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime);
+    public void RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime, ColorSpace processingColorSpace);
 }

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IPreviewRenderable.cs

@@ -1,12 +1,13 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 public interface IPreviewRenderable
 {
     public RectD? GetPreviewBounds(int frame, string elementToRenderName = ""); 
-    public bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame,
+    public bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName);
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs

@@ -14,7 +14,7 @@ public interface IReadOnlyNode
     public IReadOnlyList<IReadOnlyKeyFrameData> KeyFrames { get; }
     public VecD Position { get; }
     string DisplayName { get; }
-
+    
     public void Execute(RenderContext context);
     
     /// <summary>

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs

@@ -1,6 +1,7 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
@@ -12,7 +13,7 @@ public class SceneObjectRenderContext : RenderContext
     public RenderOutputProperty TargetPropertyOutput { get; }
 
     public SceneObjectRenderContext(RenderOutputProperty targetPropertyOutput, DrawingSurface surface, RectD localBounds, KeyFrameTime frameTime,
-        ChunkResolution chunkResolution, VecI docSize, bool renderSurfaceIsScene, double opacity) : base(surface, frameTime, chunkResolution, docSize, opacity)
+        ChunkResolution chunkResolution, VecI docSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, double opacity) : base(surface, frameTime, chunkResolution, docSize, processingColorSpace, opacity)
     {
         TargetPropertyOutput = targetPropertyOutput;
         LocalBounds = localBounds;

+ 14 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -71,6 +71,7 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
     public void Execute(RenderContext context)
     {
         if (OutputNode == null) return;
+        if(!CanExecute()) return;
 
         var queue = CalculateExecutionQueue(OutputNode);
         
@@ -88,4 +89,17 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
             }
         }
     }
+    
+    private bool CanExecute()
+    {
+        foreach (var node in Nodes)
+        {
+            if (node.IsDisposed)
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }

+ 1 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineChannelsNode.cs

@@ -119,15 +119,13 @@ public class CombineChannelsNode : RenderNode
         return finalBounds;
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         if (Red.Value == null && Green.Value == null && Blue.Value == null && Alpha.Value == null)
         {
             return false;
         }
 
-        RenderContext context = new(renderOn, frame, resolution, VecI.One);
-        
         OnPaint(context, renderOn); 
         
         return true;

+ 2 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateChannelsNode.cs

@@ -97,18 +97,16 @@ public class SeparateChannelsNode : Node, IRenderInput, IPreviewRenderable
         return bounds;
     }
 
-    public bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         if (Image.Value == null)
             return false;
 
-        RectD? bounds = GetPreviewBounds(frame, elementToRenderName);
+        RectD? bounds = GetPreviewBounds(context.FrameTime.Frame, elementToRenderName);
         
         if (bounds == null)
             return false;
         
-        RenderContext context = new(renderOn, frame, resolution, VecI.One);
-
         renderOn.Canvas.Save();
 
         _paint.ColorFilter = Grayscale.Value ? _redGrayscaleFilter : _redFilter;

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -36,14 +36,14 @@ public class CreateImageNode : Node
             return;
         }
 
-        var surface = RequestTexture(0, Size.Value, false);
+        var surface = RequestTexture(0, Size.Value, context.ProcessingColorSpace, false);
 
         surface.DrawingSurface.Canvas.Clear(Fill.Value);
 
         int saved = surface.DrawingSurface.Canvas.Save();
 
         RenderContext ctx = new RenderContext(surface.DrawingSurface, context.FrameTime, context.ChunkResolution,
-            context.DocumentSize);
+            context.DocumentSize, context.ProcessingColorSpace);
 
         Content.Value?.Paint(ctx, surface.DrawingSurface);
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/DebugBlendModeNode.cs

@@ -36,7 +36,7 @@ public class DebugBlendModeNode : Node
             return;
 
         var size = new VecI(Math.Max(src.Size.X, dst.Size.X), int.Max(src.Size.Y, dst.Size.Y));
-        var workingSurface = RequestTexture(0, size);
+        var workingSurface = RequestTexture(0, size, context.ProcessingColorSpace);
 
         workingSurface.DrawingSurface.Canvas.DrawSurface(dst.DrawingSurface, 0, 0, blendModeOpacityPaint);
 

+ 1 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs

@@ -40,14 +40,12 @@ public class ApplyFilterNode : RenderNode, IRenderInput
         return PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame,
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
     {
         if (Background.Value == null)
             return false;
 
-        RenderContext context = new(renderOn, frame, ChunkResolution.Full, VecI.One);
-
         int layer = renderOn.Canvas.SaveLayer(_paint);
         Background.Value.Paint(context, renderOn);
         renderOn.Canvas.RestoreToCount(layer);

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs

@@ -83,7 +83,7 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource, IPrev
     private void RenderFolderContent(SceneObjectRenderContext sceneContext, RectD bounds, bool useFilters)
     {
         VecI size = (VecI)bounds.Size;
-        var outputWorkingSurface = RequestTexture(0, size, true);
+        var outputWorkingSurface = RequestTexture(0, size, sceneContext.ProcessingColorSpace, true);
 
         blendPaint.ImageFilter = null;
         blendPaint.ColorFilter = null;
@@ -94,7 +94,7 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource, IPrev
 
         if (Background.Value != null && sceneContext.TargetPropertyOutput != RawOutput)
         {
-            Texture tempSurface = RequestTexture(1, outputWorkingSurface.Size);
+            Texture tempSurface = RequestTexture(1, outputWorkingSurface.Size, sceneContext.ProcessingColorSpace);
             if (Background.Connection.Node is IClipSource clipSource && ClipToPreviousMember)
             {
                 DrawClipSource(tempSurface.DrawingSurface, clipSource, sceneContext);
@@ -201,12 +201,12 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource, IPrev
         return GetTightBounds(frame);
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame,
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
     {
         if (elementToRenderName == nameof(EmbeddedMask))
         {
-            return base.RenderPreview(renderOn, resolution, frame, elementToRenderName);
+            return base.RenderPreview(renderOn, context, elementToRenderName);
         }
 
         // TODO: Make preview better, with filters, clips and stuff
@@ -219,7 +219,7 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource, IPrev
                 IReadOnlyNode node = executionQueue.Dequeue();
                 if (node is IPreviewRenderable previewRenderable)
                 {
-                    previewRenderable.RenderPreview(renderOn, resolution, frame, elementToRenderName);
+                    previewRenderable.RenderPreview(renderOn, context, elementToRenderName);
                 }
             }
         }

+ 9 - 8
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 
@@ -136,7 +137,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         }
     }
 
-    public override bool RenderPreview(DrawingSurface renderOnto, ChunkResolution resolution, int frame,
+    public override bool RenderPreview(DrawingSurface renderOnto, RenderContext context,
         string elementToRenderName)
     {
         if (IsDisposed)
@@ -146,10 +147,10 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
         if (elementToRenderName == nameof(EmbeddedMask))
         {
-            return base.RenderPreview(renderOnto, resolution, frame, elementToRenderName);
+            return base.RenderPreview(renderOnto, context, elementToRenderName);
         }
 
-        var img = GetLayerImageAtFrame(frame);
+        var img = GetLayerImageAtFrame(context.FrameTime.Frame);
 
         if (Guid.TryParse(elementToRenderName, out Guid guid))
         {
@@ -166,7 +167,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
             return false;
         }
 
-        if (renderedSurfaceFrame == frame)
+        if (renderedSurfaceFrame == context.FrameTime.Frame)
         {
             renderOnto.Canvas.DrawSurface(fullResrenderedSurface.DrawingSurface, VecI.Zero, blendPaint);
         }
@@ -174,7 +175,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         {
             img.DrawMostUpToDateRegionOn(
                 new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
-                resolution,
+                context.ChunkResolution,
                 renderOnto, VecI.Zero, blendPaint);
         }
 
@@ -239,13 +240,13 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
     void IReadOnlyImageNode.ForEveryFrame(Action<IReadOnlyChunkyImage> action) => ForEveryFrame(action);
 
-    public override void RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime)
+    public override void RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime, ColorSpace processColorSpace)
     {
-        base.RenderChunk(chunkPos, resolution, frameTime);
+        base.RenderChunk(chunkPos, resolution, frameTime, processColorSpace);
 
         var img = GetLayerImageAtFrame(frameTime.Frame);
 
-        RenderChunkyImageChunk(chunkPos, resolution, img, 85, ref fullResrenderedSurface);
+        RenderChunkyImageChunk(chunkPos, resolution, img, 85, processColorSpace, ref fullResrenderedSurface);
         renderedSurfaceFrame = frameTime.Frame;
     }
 

+ 1 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs

@@ -103,14 +103,13 @@ public class MergeNode : RenderNode
         return totalBounds;
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         if (Top.Value == null && Bottom.Value == null)
         {
             return false;
         }
 
-        RenderContext context = new RenderContext(renderOn, frame, ChunkResolution.Full, VecI.Zero);
         Merge(renderOn, context);
 
         return true;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageLeftNode.cs

@@ -55,7 +55,7 @@ public class ModifyImageLeftNode : Node, IPairNode, IPreviewRenderable
         return new RectD(0, 0, Image.Value.Size.X, Image.Value.Size.Y);
     }
 
-    public bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         if(Image.Value is null)
         {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageRightNode.cs

@@ -110,7 +110,7 @@ public class ModifyImageRightNode : RenderNode, IPairNode, ICustomShaderNode
         return null;
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         //TODO: Implement
         return false;

+ 16 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -10,6 +10,7 @@ using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Shaders.Generation;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -41,7 +42,7 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
     protected virtual bool ExecuteOnlyOnCacheChange => false;
 
-    protected bool IsDisposed => _isDisposed;
+    protected internal bool IsDisposed => _isDisposed;
     private bool _isDisposed;
 
     private Dictionary<int, Texture> _managedTextures = new();
@@ -83,14 +84,14 @@ public abstract class Node : IReadOnlyNode, IDisposable
         }
     }
 
-    protected Texture RequestTexture(int id, VecI size, bool clear = true)
+    protected Texture RequestTexture(int id, VecI size, ColorSpace processingCs, bool clear = true)
     {
         if (_managedTextures.TryGetValue(id, out var texture))
         {
-            if (texture.Size != size || texture.IsDisposed)
+            if (texture.Size != size || texture.IsDisposed || texture.ColorSpace != processingCs)
             {
                 texture.Dispose();
-                texture = new Texture(size);
+                texture = new Texture(CreateImageInfo(size, processingCs));
                 _managedTextures[id] = texture;
                 return texture;
             }
@@ -103,9 +104,19 @@ public abstract class Node : IReadOnlyNode, IDisposable
             return texture;
         }
 
-        _managedTextures[id] = new Texture(size);
+        _managedTextures[id] = new Texture(CreateImageInfo(size, processingCs));
         return _managedTextures[id];
     }
+    
+    private ImageInfo CreateImageInfo(VecI size, ColorSpace processingCs)
+    {
+        if (processingCs == null)
+        {
+            return new ImageInfo(size.X, size.Y, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgbLinear()) { GpuBacked = true};
+        }
+
+        return new ImageInfo(size.X, size.Y, ColorType.RgbaF16, AlphaType.Premul, processingCs) { GpuBacked = true };
+    }
 
     public void TraverseBackwards(Func<IReadOnlyNode, bool> action)
     {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NoiseNode.cs

@@ -94,7 +94,7 @@ public class NoiseNode : RenderNode
         return new RectD(0, 0, 128, 128); 
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         var shader = SelectShader();
         if (shader == null)

+ 1 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -48,14 +48,13 @@ public class OutputNode : Node, IRenderInput, IPreviewRenderable
         return new RectD(0, 0, lastDocumentSize.Value.X, lastDocumentSize.Value.Y); 
     }
 
-    public bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         if (Input.Value == null)
         {
             return false;
         }
         
-        RenderContext context = new(renderOn, frame, resolution, VecI.One);
         int saved = renderOn.Canvas.Save();
         Input.Value.Paint(context, renderOn);
         

+ 2 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs

@@ -2,7 +2,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
-using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -40,7 +39,7 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
                                && surface.DeviceClipBounds.Size != context.DocumentSize;
         if (useIntermediate)
         {
-            Texture intermediate = RequestTexture(0, context.DocumentSize);
+            Texture intermediate = RequestTexture(0, context.DocumentSize, context.ProcessingColorSpace);
             target = intermediate.DrawingSurface;
         }
 
@@ -56,6 +55,6 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
 
     public abstract RectD? GetPreviewBounds(int frame, string elementToRenderName = "");
 
-    public abstract bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame,
+    public abstract bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName);
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RasterizeShapeNode.cs

@@ -35,7 +35,7 @@ public class RasterizeShapeNode : RenderNode
         return Data?.Value?.TransformedAABB;
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         var shape = Data.Value;
 

+ 0 - 14
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/ShapeNode.cs

@@ -23,21 +23,7 @@ public abstract class ShapeNode<T> : Node where T : ShapeVectorData
         var data = GetShapeData(context);
 
         Output.Value = data;
-        
-        /*if (data == null || !data.IsValid())
-            return;
-
-        return RasterizePreview(data, context.DocumentSize);*/
     }
     
     protected abstract T? GetShapeData(RenderContext context);
-
-    public Texture RasterizePreview(ShapeVectorData vectorData, VecI size)
-    {
-        Texture texture = RequestTexture(0, size);
-        
-        vectorData.RasterizeTransformed(texture.DrawingSurface);
-        
-        return texture;
-    }
 }

+ 7 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs

@@ -7,6 +7,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
@@ -144,6 +145,7 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
             context.FrameTime, context.ChunkResolution, context.DocumentSize, renderTarget == context.RenderSurface,
+            context.ProcessingColorSpace,
             context.Opacity);
         renderObjectContext.FullRerender = context.FullRerender;
         return renderObjectContext;
@@ -192,13 +194,13 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         maskCacheHash = EmbeddedMask?.GetCacheHash() ?? 0;
     }
 
-    public virtual void RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime)
+    public virtual void RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime, ColorSpace processingColorSpace)
     {
-        RenderChunkyImageChunk(chunkPos, resolution, EmbeddedMask, 55, ref renderedMask);
+        RenderChunkyImageChunk(chunkPos, resolution, EmbeddedMask, 55, processingColorSpace, ref renderedMask);
     }
 
     protected void RenderChunkyImageChunk(VecI chunkPos, ChunkResolution resolution, ChunkyImage img,
-        int textureId,
+        int textureId, ColorSpace processingColorSpace,
         ref Texture? renderSurface)
     {
         if (img is null)
@@ -208,7 +210,7 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
         VecI targetSize = img.LatestSize;
         
-        renderSurface = RequestTexture(textureId, targetSize, false);
+        renderSurface = RequestTexture(textureId, targetSize, processingColorSpace, false);
 
         int saved = renderSurface.DrawingSurface.Canvas.Save();
         
@@ -300,7 +302,7 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         return null;
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame,
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
     {
         if (elementToRenderName != nameof(EmbeddedMask))

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs

@@ -85,7 +85,7 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
         return null;
     }
 
-    public override bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame,
+    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
     {
         if (ShapeData == null)

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -102,4 +102,5 @@ public interface IReadOnlyDocument : IDisposable
     IReadOnlyList<IReadOnlyStructureNode> FindMemberPath(Guid guid);
     IReadOnlyReferenceLayer? ReferenceLayer { get; }
     public DocumentRenderer Renderer { get; }
+    public ColorSpace ProcessingColorSpace { get; }
 }

+ 7 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs

@@ -4,6 +4,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 
@@ -19,10 +20,13 @@ internal class FloodFillChunkCache : IDisposable
     private readonly int frame;
 
     private readonly Dictionary<VecI, OneOf<Chunk, EmptyChunk>> acquiredChunks = new();
+    
+    private ColorSpace processingColorSpace = ColorSpace.CreateSrgbLinear();
 
     public FloodFillChunkCache(IReadOnlyChunkyImage image)
     {
         this.image = image;
+        this.processingColorSpace = image.ProcessingColorSpace;
     }
 
     public FloodFillChunkCache(HashSet<Guid> membersToRender, IReadOnlyDocument document, int frame)
@@ -30,6 +34,7 @@ internal class FloodFillChunkCache : IDisposable
         this.membersToRender = membersToRender;
         this.document = document;
         this.frame = frame;
+        processingColorSpace = document.ProcessingColorSpace;
     }
 
     public bool ChunkExistsInStorage(VecI pos)
@@ -50,7 +55,7 @@ internal class FloodFillChunkCache : IDisposable
         {
             if (document is null || membersToRender is null)
                 throw new InvalidOperationException();
-            Chunk chunk = Chunk.Create();
+            Chunk chunk = Chunk.Create(processingColorSpace);
             chunk.Surface.DrawingSurface.Canvas.Save();
             
             VecI chunkPos = pos * ChunkyImage.FullChunkSize;
@@ -68,7 +73,7 @@ internal class FloodFillChunkCache : IDisposable
         // there is only a single image, just get the chunk from it
         if (!image.LatestOrCommittedChunkExists(pos))
             return new EmptyChunk();
-        Chunk chunkOnImage = Chunk.Create(ChunkResolution.Full);
+        Chunk chunkOnImage = Chunk.Create(processingColorSpace, ChunkResolution.Full);
 
         if (!image.DrawMostUpToDateChunkOn(pos, ChunkResolution.Full, chunkOnImage.Surface.DrawingSurface, VecI.Zero, ReplacingPaint))
         {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -84,7 +84,7 @@ public static class FloodFillHelper
 
             if (!drawingChunks.ContainsKey(chunkPos))
             {
-                var chunk = Chunk.Create();
+                var chunk = Chunk.Create(document.ProcessingColorSpace);
                 chunk.Surface.DrawingSurface.Canvas.Clear(Colors.Transparent);
                 drawingChunks[chunkPos] = chunk;
             }

+ 37 - 0
src/PixiEditor.ChangeableDocument/Changes/Properties/ChangeProcessingColorSpace_Change.cs

@@ -0,0 +1,37 @@
+using Drawie.Backend.Core.Surfaces.ImageData;
+using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
+
+namespace PixiEditor.ChangeableDocument.Changes.Properties;
+
+internal class ChangeProcessingColorSpace_Change : Change
+{
+    private ColorSpace toColorSpace;
+    private ColorSpace original;
+    
+    [GenerateMakeChangeAction]
+    public ChangeProcessingColorSpace_Change(ColorSpace newColorSpace)
+    {
+        this.toColorSpace = newColorSpace;
+    }
+    
+    public override bool InitializeAndValidate(Document target)
+    {
+        original = target.ProcessingColorSpace;
+        return true;  
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        ignoreInUndo = false;
+        target.ProcessingColorSpace = toColorSpace;
+
+        return new ProcessingColorSpace_ChangeInfo(toColorSpace);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.ProcessingColorSpace = original;
+        
+        return new ProcessingColorSpace_ChangeInfo(original);
+    }
+}

+ 5 - 6
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -34,7 +34,7 @@ public class DocumentRenderer : IPreviewRenderable
             {
                 if (node is IChunkRenderable imageNode)
                 {
-                    imageNode.RenderChunk(chunkPos, resolution, frameTime);
+                    imageNode.RenderChunk(chunkPos, resolution, frameTime, Document.ProcessingColorSpace);
                 }
             }));
         }
@@ -47,7 +47,7 @@ public class DocumentRenderer : IPreviewRenderable
         ChunkResolution resolution)
     {
         IsBusy = true;
-        RenderContext context = new(toDrawOn, frame, resolution, Document.Size);
+        RenderContext context = new(toDrawOn, frame, resolution, Document.Size, Document.ProcessingColorSpace);
         context.FullRerender = true;
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
         try
@@ -75,7 +75,7 @@ public class DocumentRenderer : IPreviewRenderable
         
         IsBusy = true;
 
-        RenderContext context = new(renderOn, frameTime, resolution, Document.Size);
+        RenderContext context = new(renderOn, frameTime, resolution, Document.Size, Document.ProcessingColorSpace);
         context.FullRerender = true;
         
         node.RenderForOutput(context, renderOn, null);
@@ -123,10 +123,9 @@ public class DocumentRenderer : IPreviewRenderable
     public RectD? GetPreviewBounds(int frame, string elementNameToRender = "") =>
         new(0, 0, Document.Size.X, Document.Size.Y);
 
-    public bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame,
+    public bool RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
     {
-        RenderContext context = new(renderOn, frame, resolution, Document.Size);
         Document.NodeGraph.Execute(context);
 
         return true;
@@ -135,7 +134,7 @@ public class DocumentRenderer : IPreviewRenderable
     public void RenderDocument(DrawingSurface toRenderOn, KeyFrameTime frameTime)
     {
         IsBusy = true;
-        RenderContext context = new(toRenderOn, frameTime, ChunkResolution.Full, Document.Size) { FullRerender = true };
+        RenderContext context = new(toRenderOn, frameTime, ChunkResolution.Full, Document.Size, Document.ProcessingColorSpace) { FullRerender = true };
         Document.NodeGraph.Execute(context);
         IsBusy = false;
     }

+ 5 - 1
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 using DrawingApiBlendMode = Drawie.Backend.Core.Surfaces.BlendMode;
@@ -16,16 +17,19 @@ public class RenderContext
     
     public DrawingSurface RenderSurface { get; set; }
     public bool FullRerender { get; set; } = false;
+    
+    public ColorSpace ProcessingColorSpace { get; set; } 
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
-        VecI docSize, double opacity = 1) 
+        VecI docSize, ColorSpace processingColorSpace, double opacity = 1) 
     {
         RenderSurface = renderSurface;
         FrameTime = frameTime;
         ChunkResolution = chunkResolution;
         DocumentSize = docSize;
         Opacity = opacity;
+        ProcessingColorSpace = processingColorSpace;
     }
 
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)

+ 256 - 0
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.deps.json

@@ -0,0 +1,256 @@
+{
+  "runtimeTarget": {
+    "name": ".NETStandard,Version=v2.0/",
+    "signature": ""
+  },
+  "compilationOptions": {},
+  "targets": {
+    ".NETStandard,Version=v2.0": {},
+    ".NETStandard,Version=v2.0/": {
+      "PixiEditor.Api.CGlueMSBuild/1.0.0": {
+        "dependencies": {
+          "Microsoft.Build.Utilities.Core": "17.12.6",
+          "Mono.Cecil": "0.11.6",
+          "NETStandard.Library": "2.0.3",
+          "StyleCop.Analyzers": "1.1.118"
+        },
+        "runtime": {
+          "PixiEditor.Api.CGlueMSBuild.dll": {}
+        }
+      },
+      "Microsoft.Build.Framework/17.12.6": {
+        "dependencies": {
+          "Microsoft.Win32.Registry": "5.0.0",
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0",
+          "System.Security.Principal.Windows": "5.0.0"
+        }
+      },
+      "Microsoft.Build.Utilities.Core/17.12.6": {
+        "dependencies": {
+          "Microsoft.Build.Framework": "17.12.6",
+          "Microsoft.NET.StringTools": "17.12.6",
+          "Microsoft.Win32.Registry": "5.0.0",
+          "System.Collections.Immutable": "8.0.0",
+          "System.Configuration.ConfigurationManager": "8.0.0",
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0",
+          "System.Security.Principal.Windows": "5.0.0",
+          "System.Text.Encoding.CodePages": "7.0.0"
+        }
+      },
+      "Microsoft.NET.StringTools/17.12.6": {
+        "dependencies": {
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+        }
+      },
+      "Microsoft.NETCore.Platforms/1.1.0": {},
+      "Microsoft.Win32.Registry/5.0.0": {
+        "dependencies": {
+          "System.Buffers": "4.5.1",
+          "System.Memory": "4.5.5",
+          "System.Security.AccessControl": "5.0.0",
+          "System.Security.Principal.Windows": "5.0.0"
+        }
+      },
+      "Mono.Cecil/0.11.6": {
+        "runtime": {
+          "lib/netstandard2.0/Mono.Cecil.Mdb.dll": {
+            "assemblyVersion": "0.11.6.0",
+            "fileVersion": "0.11.6.0"
+          },
+          "lib/netstandard2.0/Mono.Cecil.Pdb.dll": {
+            "assemblyVersion": "0.11.6.0",
+            "fileVersion": "0.11.6.0"
+          },
+          "lib/netstandard2.0/Mono.Cecil.Rocks.dll": {
+            "assemblyVersion": "0.11.6.0",
+            "fileVersion": "0.11.6.0"
+          },
+          "lib/netstandard2.0/Mono.Cecil.dll": {
+            "assemblyVersion": "0.11.6.0",
+            "fileVersion": "0.11.6.0"
+          }
+        }
+      },
+      "NETStandard.Library/2.0.3": {
+        "dependencies": {
+          "Microsoft.NETCore.Platforms": "1.1.0"
+        }
+      },
+      "StyleCop.Analyzers/1.1.118": {},
+      "System.Buffers/4.5.1": {},
+      "System.Collections.Immutable/8.0.0": {
+        "dependencies": {
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+        }
+      },
+      "System.Configuration.ConfigurationManager/8.0.0": {
+        "dependencies": {
+          "System.Security.Cryptography.ProtectedData": "8.0.0"
+        }
+      },
+      "System.Memory/4.5.5": {
+        "dependencies": {
+          "System.Buffers": "4.5.1",
+          "System.Numerics.Vectors": "4.4.0",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+        }
+      },
+      "System.Numerics.Vectors/4.4.0": {},
+      "System.Runtime.CompilerServices.Unsafe/6.0.0": {},
+      "System.Security.AccessControl/5.0.0": {
+        "dependencies": {
+          "System.Security.Principal.Windows": "5.0.0"
+        }
+      },
+      "System.Security.Cryptography.ProtectedData/8.0.0": {
+        "dependencies": {
+          "System.Memory": "4.5.5"
+        }
+      },
+      "System.Security.Principal.Windows/5.0.0": {},
+      "System.Text.Encoding.CodePages/7.0.0": {
+        "dependencies": {
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+        }
+      }
+    }
+  },
+  "libraries": {
+    "PixiEditor.Api.CGlueMSBuild/1.0.0": {
+      "type": "project",
+      "serviceable": false,
+      "sha512": ""
+    },
+    "Microsoft.Build.Framework/17.12.6": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-jleteC0seumLGTmTVwob97lcwPj/dfgzL/V3g/VVcMZgo2Ic7jzdy8AYpByPDh8e3uRq0SjCl6HOFCjhy5GzRQ==",
+      "path": "microsoft.build.framework/17.12.6",
+      "hashPath": "microsoft.build.framework.17.12.6.nupkg.sha512"
+    },
+    "Microsoft.Build.Utilities.Core/17.12.6": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-pU3GnHcXp8VRMGKxdJCq+tixfhFn+QwEbpqmZmc/nqFHFyuhlGwjonWZMIWcwuCv/8EHgxoOttFvna1vrN+RrA==",
+      "path": "microsoft.build.utilities.core/17.12.6",
+      "hashPath": "microsoft.build.utilities.core.17.12.6.nupkg.sha512"
+    },
+    "Microsoft.NET.StringTools/17.12.6": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-w8Ehofqte5bJoR+Fa3f6JwkwFEkGtXxqvQHGOVOSHDzgNVySvL5FSNhavbQSZ864el9c3rjdLPLAtBW8dq6fmg==",
+      "path": "microsoft.net.stringtools/17.12.6",
+      "hashPath": "microsoft.net.stringtools.17.12.6.nupkg.sha512"
+    },
+    "Microsoft.NETCore.Platforms/1.1.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
+      "path": "microsoft.netcore.platforms/1.1.0",
+      "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
+    },
+    "Microsoft.Win32.Registry/5.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
+      "path": "microsoft.win32.registry/5.0.0",
+      "hashPath": "microsoft.win32.registry.5.0.0.nupkg.sha512"
+    },
+    "Mono.Cecil/0.11.6": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-f33RkDtZO8VlGXCtmQIviOtxgnUdym9xx/b1p9h91CRGOsJFxCFOFK1FDbVt1OCf1aWwYejUFa2MOQyFWTFjbA==",
+      "path": "mono.cecil/0.11.6",
+      "hashPath": "mono.cecil.0.11.6.nupkg.sha512"
+    },
+    "NETStandard.Library/2.0.3": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
+      "path": "netstandard.library/2.0.3",
+      "hashPath": "netstandard.library.2.0.3.nupkg.sha512"
+    },
+    "StyleCop.Analyzers/1.1.118": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==",
+      "path": "stylecop.analyzers/1.1.118",
+      "hashPath": "stylecop.analyzers.1.1.118.nupkg.sha512"
+    },
+    "System.Buffers/4.5.1": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
+      "path": "system.buffers/4.5.1",
+      "hashPath": "system.buffers.4.5.1.nupkg.sha512"
+    },
+    "System.Collections.Immutable/8.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
+      "path": "system.collections.immutable/8.0.0",
+      "hashPath": "system.collections.immutable.8.0.0.nupkg.sha512"
+    },
+    "System.Configuration.ConfigurationManager/8.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-JlYi9XVvIREURRUlGMr1F6vOFLk7YSY4p1vHo4kX3tQ0AGrjqlRWHDi66ImHhy6qwXBG3BJ6Y1QlYQ+Qz6Xgww==",
+      "path": "system.configuration.configurationmanager/8.0.0",
+      "hashPath": "system.configuration.configurationmanager.8.0.0.nupkg.sha512"
+    },
+    "System.Memory/4.5.5": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
+      "path": "system.memory/4.5.5",
+      "hashPath": "system.memory.4.5.5.nupkg.sha512"
+    },
+    "System.Numerics.Vectors/4.4.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==",
+      "path": "system.numerics.vectors/4.4.0",
+      "hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512"
+    },
+    "System.Runtime.CompilerServices.Unsafe/6.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
+      "path": "system.runtime.compilerservices.unsafe/6.0.0",
+      "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
+    },
+    "System.Security.AccessControl/5.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==",
+      "path": "system.security.accesscontrol/5.0.0",
+      "hashPath": "system.security.accesscontrol.5.0.0.nupkg.sha512"
+    },
+    "System.Security.Cryptography.ProtectedData/8.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==",
+      "path": "system.security.cryptography.protecteddata/8.0.0",
+      "hashPath": "system.security.cryptography.protecteddata.8.0.0.nupkg.sha512"
+    },
+    "System.Security.Principal.Windows/5.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==",
+      "path": "system.security.principal.windows/5.0.0",
+      "hashPath": "system.security.principal.windows.5.0.0.nupkg.sha512"
+    },
+    "System.Text.Encoding.CodePages/7.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==",
+      "path": "system.text.encoding.codepages/7.0.0",
+      "hashPath": "system.text.encoding.codepages.7.0.0.nupkg.sha512"
+    }
+  }
+}

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


+ 3 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -779,5 +779,7 @@
   "VIEWPORT_ROTATION": "Viewport rotation",
   "NEXT_TOOL_SET": "Next tool set",
   "PREVIOUS_TOOL_SET": "Previous tool set",
-  "FILL_MODE": "Fill mode"
+  "FILL_MODE": "Fill mode",
+  "USE_LINEAR_SRGB_PROCESSING": "Use linear sRGB for processing colors",
+    "USE_LINEAR_SRGB_PROCESSING_DESC": "Convert document using legacy blending mode to linear sRGB for processing colors. This will affect the colors of the document, but will make blending more accurate."
 }

+ 1 - 1
src/PixiEditor/Helpers/Behaviours/TextBlockExtensions.cs

@@ -20,7 +20,7 @@ internal class TextBlockExtensions : AvaloniaObject
 
     private static void OnBindableInlinesChanged(AvaloniaPropertyChangedEventArgs<IEnumerable<Inline>> e)
     {
-        if (e.Sender is not TextBlock target)
+        if (e.Sender is not TextBlock target || e.NewValue.Value is null)
         {
             return;
         }

+ 7 - 0
src/PixiEditor/Helpers/DocumentViewModelBuilder.cs

@@ -31,6 +31,7 @@ internal class DocumentViewModelBuilder
 
     public NodeGraphBuilder Graph { get; set; }
     public string ImageEncoderUsed { get; set; } = "QOI";
+    public bool UsesLegacyColorBlending { get; set; } = false;
 
     public DocumentViewModelBuilder WithSize(int width, int height)
     {
@@ -121,6 +122,12 @@ internal class DocumentViewModelBuilder
         ImageEncoderUsed = encoder;
         return this;
     }
+    
+    public DocumentViewModelBuilder WithLegacyColorBlending(bool usesLegacyColorBlending)
+    {
+        UsesLegacyColorBlending = usesLegacyColorBlending;
+        return this;
+    }
 
     private static void BuildKeyFrames(List<KeyFrameGroup> root, List<KeyFrameBuilder> data)
     {

+ 1 - 0
src/PixiEditor/Helpers/Extensions/PixiParserDocumentEx.cs

@@ -30,6 +30,7 @@ internal static class PixiParserDocumentEx
 
         return DocumentViewModel.Build(b => b
             .WithSerializerData(document.SerializerName, document.SerializerVersion)
+            .WithLegacyColorBlending(document.LegacyColorBlending)
             .WithSize(document.Width, document.Height)
             .WithImageEncoder(document.ImageEncoderUsed)
             .WithPalette(document.Palette, color => new PaletteColor(color.R, color.G, color.B))

+ 8 - 0
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -204,6 +204,9 @@ internal class DocumentUpdater
             case SetPlayingState_PassthroughAction info:
                 ProcessPlayAnimation(info);
                 break;
+            case ProcessingColorSpace_ChangeInfo info:
+                ProcessProcessingColorSpace(info);
+                break;
         }
     }
 
@@ -708,4 +711,9 @@ internal class DocumentUpdater
     {
         doc.AnimationHandler.SetOnionFrames(info.OnionFrames, info.Opacity);
     }
+    
+    private void ProcessProcessingColorSpace(ProcessingColorSpace_ChangeInfo info)
+    {
+        doc.SetProcessingColorSpace(info.NewColorSpace);
+    }
 }

+ 6 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -8,6 +8,7 @@ using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Enums;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Vector;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Models.Clipboard;
@@ -812,4 +813,9 @@ internal class DocumentOperationsModule : IDocumentOperations
 
         Internals.ActionAccumulator.AddActions(targetAction);
     }
+
+    public void UseLinearSrgbProcessing()
+    {
+        Internals.ActionAccumulator.AddFinishedActions(new ChangeProcessingColorSpace_Action(ColorSpace.CreateSrgbLinear()));
+    }
 }

+ 2 - 0
src/PixiEditor/Models/Handlers/IDocument.cs

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Vector;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Helpers;
@@ -54,6 +55,7 @@ internal interface IDocument : IHandler
     public void SetHorizontalSymmetryAxisEnabled(bool infoState);
     public void SetVerticalSymmetryAxisEnabled(bool infoState);
     public void UpdateSelectionPath(VectorPath infoNewPath);
+    public void SetProcessingColorSpace(ColorSpace infoColorSpace);
     public void SetSize(VecI infoSize);
     public Color PickColor(VecD controllerLastPrecisePosition, DocumentScope scope, bool includeReference, bool includeCanvas, int frame, bool isTopMost);
     public List<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false);

+ 4 - 0
src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs

@@ -189,6 +189,10 @@ internal class AffectedAreasGatherer
                     AddToImagePreviews(info.LayerId, info.Affected);
                     AddToNodePreviews(info.LayerId);
                     break;
+                case ProcessingColorSpace_ChangeInfo:
+                    AddWholeCanvasToMainImage();
+                    AddWholeCanvasToEveryImagePreview();
+                    break;
             }
         }
     }

+ 3 - 2
src/PixiEditor/Models/Rendering/AnimationPreviewRenderer.cs

@@ -4,6 +4,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using Drawie.Backend.Core.Surfaces;
 using PixiEditor.Models.DocumentModels;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.Models.Rendering;
 
@@ -21,7 +22,7 @@ internal class AnimationKeyFramePreviewRenderer(DocumentInternalParts internals)
         return null;
     }
 
-    public bool RenderPreview(DrawingSurface renderOn, ChunkResolution resolution, int frame, string elementToRenderName)
+    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
         if (internals.Tracker.Document.AnimationData.TryFindKeyFrame(
                 Guid.Parse(elementToRenderName),
@@ -32,7 +33,7 @@ internal class AnimationKeyFramePreviewRenderer(DocumentInternalParts internals)
             
             if (node is IPreviewRenderable previewRenderable)
             {
-                return previewRenderable.RenderPreview(renderOn, resolution, frame, elementToRenderName);
+                return previewRenderable.RenderPreview(renderOn, context, elementToRenderName);
             }
         }
         

+ 54 - 40
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -9,6 +9,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentModels;
@@ -22,7 +23,7 @@ internal class MemberPreviewUpdater
 {
     private readonly IDocument doc;
     private readonly DocumentInternalParts internals;
-    
+
     private AnimationKeyFramePreviewRenderer AnimationKeyFramePreviewRenderer { get; }
 
     public MemberPreviewUpdater(IDocument doc, DocumentInternalParts internals)
@@ -69,15 +70,9 @@ internal class MemberPreviewUpdater
         var previewSize = StructureHelpers.CalculatePreviewSize(internals.Tracker.Document.Size);
         float scaling = (float)previewSize.X / doc.SizeBindable.X;
 
-        if (doc.PreviewPainter == null)
-        {
-            doc.PreviewPainter = new PreviewPainter(doc.Renderer);
-            doc.PreviewPainter.Repaint();
-        }
-        else
-        {
-            doc.PreviewPainter.Repaint();
-        }
+        doc.PreviewPainter = new PreviewPainter(doc.Renderer, doc.AnimationHandler.ActiveFrameTime,
+            doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+        doc.PreviewPainter.Repaint();
     }
 
     private void RenderLayersPreview(Guid[] memberGuids)
@@ -96,11 +91,18 @@ internal class MemberPreviewUpdater
                         continue;
 
                     structureMemberHandler.PreviewPainter =
-                        new PreviewPainter(previewRenderable);
+                        new PreviewPainter(previewRenderable,
+                            doc.AnimationHandler.ActiveFrameTime, doc.SizeBindable,
+                            internals.Tracker.Document.ProcessingColorSpace);
                     structureMemberHandler.PreviewPainter.Repaint();
                 }
                 else
                 {
+                    structureMemberHandler.PreviewPainter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
+                    structureMemberHandler.PreviewPainter.DocumentSize = doc.SizeBindable;
+                    structureMemberHandler.PreviewPainter.ProcessingColorSpace =
+                        internals.Tracker.Document.ProcessingColorSpace;
+                    
                     structureMemberHandler.PreviewPainter.Repaint();
                 }
             }
@@ -131,32 +133,40 @@ internal class MemberPreviewUpdater
             }
         }
     }
-    
-    private bool IsInFrame(ICelHandler iCel)
+
+    private bool IsInFrame(ICelHandler cel)
     {
-        return iCel.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
-               iCel.StartFrameBindable + iCel.DurationBindable >= doc.AnimationHandler.ActiveFrameBindable;
+        return cel.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
+               cel.StartFrameBindable + cel.DurationBindable >= doc.AnimationHandler.ActiveFrameBindable;
     }
 
-    private void RenderFramePreview(ICelHandler iCel)
+    private void RenderFramePreview(ICelHandler cel)
     {
-        if (internals.Tracker.Document.AnimationData.TryFindKeyFrame(iCel.Id, out KeyFrame _))
+        if (internals.Tracker.Document.AnimationData.TryFindKeyFrame(cel.Id, out KeyFrame _))
         {
-            iCel.PreviewPainter ??= new PreviewPainter(AnimationKeyFramePreviewRenderer, iCel.Id.ToString());
-            iCel.PreviewPainter.Repaint();
+            KeyFrameTime frameTime = doc.AnimationHandler.ActiveFrameTime;
+            cel.PreviewPainter = new PreviewPainter(AnimationKeyFramePreviewRenderer, frameTime, doc.SizeBindable,
+                internals.Tracker.Document.ProcessingColorSpace, cel.Id.ToString());
+            cel.PreviewPainter.Repaint();
         }
     }
-    
+
     private void RenderGroupPreview(ICelGroupHandler groupHandler)
     {
         var group = internals.Tracker.Document.AnimationData.KeyFrames.FirstOrDefault(x => x.Id == groupHandler.Id);
         if (group != null)
         {
-            groupHandler.PreviewPainter ??= new PreviewPainter(AnimationKeyFramePreviewRenderer, groupHandler.Id.ToString());
+            KeyFrameTime frameTime = doc.AnimationHandler.ActiveFrameTime;
+            ColorSpace processingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
+            VecI documentSize = doc.SizeBindable;
+
+            groupHandler.PreviewPainter =
+                new PreviewPainter(AnimationKeyFramePreviewRenderer, frameTime, documentSize, processingColorSpace,
+                    groupHandler.Id.ToString());
             groupHandler.PreviewPainter.Repaint();
         }
     }
-    
+
     private void RenderMaskPreviews(Guid[] members)
     {
         foreach (var node in doc.NodeGraphHandler.AllNodes)
@@ -170,17 +180,13 @@ internal class MemberPreviewUpdater
                 if (member is not IPreviewRenderable previewRenderable)
                     continue;
 
-                if (structureMemberHandler.MaskPreviewPainter == null)
-                {
-                    structureMemberHandler.MaskPreviewPainter = new PreviewPainter(
-                        previewRenderable,
-                        nameof(StructureNode.EmbeddedMask));
-                    structureMemberHandler.MaskPreviewPainter.Repaint();
-                }
-                else
-                {
-                    structureMemberHandler.MaskPreviewPainter.Repaint();
-                }
+                structureMemberHandler.MaskPreviewPainter = new PreviewPainter(
+                    previewRenderable,
+                    doc.AnimationHandler.ActiveFrameTime,
+                    doc.SizeBindable,
+                    internals.Tracker.Document.ProcessingColorSpace,
+                    nameof(StructureNode.EmbeddedMask));
+                structureMemberHandler.MaskPreviewPainter.Repaint();
             }
         }
     }
@@ -211,14 +217,22 @@ internal class MemberPreviewUpdater
                 continue;
             }
 
-            if (nodeVm.ResultPainter == null && node is IPreviewRenderable renderable)
+            if (node is IPreviewRenderable renderable)
             {
-                nodeVm.ResultPainter = new PreviewPainter(renderable);
-                nodeVm.ResultPainter.Repaint();
-            }
-            else
-            {
-                nodeVm.ResultPainter?.Repaint();
+                if (nodeVm.ResultPainter == null)
+                {
+                    nodeVm.ResultPainter = new PreviewPainter(renderable, doc.AnimationHandler.ActiveFrameTime,
+                        doc.SizeBindable, internals.Tracker.Document.ProcessingColorSpace);
+                    nodeVm.ResultPainter.Repaint();
+                }
+                else
+                {
+                    nodeVm.ResultPainter.FrameTime = doc.AnimationHandler.ActiveFrameTime;
+                    nodeVm.ResultPainter.DocumentSize = doc.SizeBindable;
+                    nodeVm.ResultPainter.ProcessingColorSpace = internals.Tracker.Document.ProcessingColorSpace;
+
+                    nodeVm.ResultPainter?.Repaint();
+                }
             }
         }
     }

+ 13 - 3
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -2,7 +2,9 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering;
 
 namespace PixiEditor.Models.Rendering;
 
@@ -10,22 +12,30 @@ public class PreviewPainter
 {
     public string ElementToRenderName { get; set; }
     public IPreviewRenderable PreviewRenderable { get; set; }
+    public ColorSpace ProcessingColorSpace { get; set; }
     public event Action RequestRepaint;
+    public KeyFrameTime FrameTime { get; set; }
+    public VecI DocumentSize { get; set; }
     
-    public PreviewPainter(IPreviewRenderable previewRenderable, string elementToRenderName = "")
+    public PreviewPainter(IPreviewRenderable previewRenderable, KeyFrameTime frameTime, VecI documentSize, ColorSpace processingColorSpace, string elementToRenderName = "")
     {
         PreviewRenderable = previewRenderable;
         ElementToRenderName = elementToRenderName;
+        ProcessingColorSpace = processingColorSpace;
+        FrameTime = frameTime;
+        DocumentSize = documentSize;
     }
 
-    public void Paint(DrawingSurface renderOn, ChunkResolution resolution, KeyFrameTime frame) 
+    public void Paint(DrawingSurface renderOn) 
     {
         if (PreviewRenderable == null)
         {
             return;
         }
 
-        PreviewRenderable.RenderPreview(renderOn, resolution, frame.Frame, ElementToRenderName);
+        RenderContext context = new(renderOn, FrameTime, ChunkResolution.Full, DocumentSize, ProcessingColorSpace);
+
+        PreviewRenderable.RenderPreview(renderOn, context, ElementToRenderName);
     }
 
     public void Repaint()

+ 5 - 8
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -1,12 +1,9 @@
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core;
-using Drawie.Backend.Core.ColorsImpl;
-using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
-using Drawie.Backend.Core.Surfaces.PaintImpl;
-using Drawie.Numerics;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
@@ -39,12 +36,12 @@ internal class SceneRenderer
         
         if (!HighResRendering || !HighDpiRenderNodePresent(Document.NodeGraph))
         {
-            texture = new(Document.Size);
+            texture = Texture.ForProcessing(Document.Size, Document.ProcessingColorSpace);
             renderTarget = texture.DrawingSurface;
         }
 
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
-            resolution, Document.Size);
+            resolution, Document.Size, Document.ProcessingColorSpace);
         Document.NodeGraph.Execute(context);
         
         if(texture != null)
@@ -90,7 +87,7 @@ internal class SceneRenderer
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
 
-            RenderContext onionContext = new(target, frame, resolution, Document.Size, finalOpacity);
+            RenderContext onionContext = new(target, frame, resolution, Document.Size, Document.ProcessingColorSpace, finalOpacity);
             Document.NodeGraph.Execute(onionContext);
         }
 
@@ -104,7 +101,7 @@ internal class SceneRenderer
             }
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
-            RenderContext onionContext = new(target, frame, resolution, Document.Size, finalOpacity);
+            RenderContext onionContext = new(target, frame, resolution, Document.Size, Document.ProcessingColorSpace, finalOpacity);
             Document.NodeGraph.Execute(onionContext);
         }
     }

+ 13 - 0
src/PixiEditor/ViewModels/Document/DocumentManagerViewModel.cs

@@ -208,4 +208,17 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
         
         ActiveDocument.Operations.CenterContent(ActiveDocument.GetSelectedMembers(), activeDocument.AnimationDataViewModel.ActiveFrameBindable);
     }
+    
+    [Command.Basic("PixiEditor.Document.UseLinearSrgbProcessing", "USE_LINEAR_SRGB_PROCESSING", "USE_LINEAR_SRGB_PROCESSING_DESC", CanExecute = "PixiEditor.DocumentUsesLegacyBlending", 
+        AnalyticsTrack = true)]
+    public void UseLinearSrgbProcessing()
+    {
+        if (ActiveDocument is null)
+            return;
+        
+        ActiveDocument.Operations.UseLinearSrgbProcessing();
+    }
+    
+    [Evaluator.CanExecute("PixiEditor.DocumentUsesLegacyBlending", nameof(ActiveDocument))]
+    public bool DocumentUsesLegacyBlending() => ActiveDocument?.UsesLegacyBlending ?? false;
 }

+ 1 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -67,6 +67,7 @@ internal partial class DocumentViewModel
         {
             SerializerName = "PixiEditor",
             SerializerVersion = VersionHelpers.GetCurrentAssemblyVersion().ToString(),
+            LegacyColorBlending = doc.ProcessingColorSpace.IsSrgb,
             Width = Width,
             Height = Height,
             Swatches = ToCollection(Swatches),

+ 31 - 75
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -215,7 +215,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     ILineOverlayHandler IDocument.LineToolOverlayHandler => LineToolOverlayViewModel;
     IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
     IAnimationHandler IDocument.AnimationHandler => AnimationDataViewModel;
-
+    public bool UsesLegacyBlending { get; private set; }
 
     private DocumentViewModel()
     {
@@ -294,6 +294,11 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
         var acc = viewModel.Internals.ActionAccumulator;
 
+        if (builderInstance.UsesLegacyColorBlending || IsFileWithOldColorBlending(serializerData))
+        {
+            acc.AddFinishedActions(new ChangeProcessingColorSpace_Action(ColorSpace.CreateSrgb()));
+        }
+
         viewModel.Internals.ChangeController.SymmetryDraggedInlet(
             new SymmetryAxisDragInfo(SymmetryAxisDirection.Horizontal, builderInstance.Height / 2));
         viewModel.Internals.ChangeController.SymmetryDraggedInlet(
@@ -417,80 +422,6 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             }
         }
 
-        /*void AddMember(Guid parentGuid, DocumentViewModelBuilder.StructureMemberBuilder member)
-        {
-            acc.AddActions(
-                new CreateStructureMember_Action(parentGuid, member.Id,
-                    member is DocumentViewModelBuilder.LayerBuilder
-                        ? StructureMemberType.Layer
-                        : StructureMemberType.Folder),
-                new StructureMemberName_Action(member.Id, member.Name)
-            );
-
-            if (!member.IsVisible)
-                acc.AddActions(new StructureMemberIsVisible_Action(member.IsVisible, member.Id));
-
-            acc.AddActions(new StructureMemberBlendMode_Action(member.BlendMode, member.Id));
-
-            acc.AddActions(new StructureMemberClipToMemberBelow_Action(member.ClipToMemberBelow, member.Id));
-
-            if (member is DocumentViewModelBuilder.LayerBuilder layerBuilder)
-            {
-                acc.AddActions(new LayerLockTransparency_Action(layerBuilder.Id, layerBuilder.LockAlpha));
-            }
-
-            if (member is DocumentViewModelBuilder.LayerBuilder layer && layer.Surface is not null)
-            {
-                PasteImage(member.Id, layer.Surface, layer.Width, layer.Height, layer.OffsetX, layer.OffsetY,
-                    false, 0);
-            }
-
-            acc.AddActions(
-                new StructureMemberOpacity_Action(member.Id, member.Opacity),
-                new EndStructureMemberOpacity_Action());
-
-            if (member.HasMask)
-            {
-                var maskSurface = member.Mask.Surface.Surface;
-
-                acc.AddActions(new CreateStructureMemberMask_Action(member.Id));
-
-                if (!member.Mask.IsVisible)
-                    acc.AddActions(new StructureMemberMaskIsVisible_Action(member.Mask.IsVisible, member.Id));
-
-                PasteImage(member.Id, member.Mask.Surface, maskSurface.Size.X, maskSurface.Size.Y, 0, 0, true, 0);
-            }
-
-            acc.AddFinishedActions();
-
-            if (member is DocumentViewModelBuilder.FolderBuilder { Children: not null } folder)
-            {
-                AddMembers(member.Id, folder.Children);
-            }
-        }*/
-
-        /*void PasteImage(Guid guid, DocumentViewModelBuilder.SurfaceBuilder surface, int width, int height, int offsetX,
-            int offsetY, bool onMask, int frame, Guid? keyFrameGuid = default)
-        {
-            acc.AddActions(
-                new PasteImage_Action(surface.Surface, new(new RectD(new VecD(offsetX, offsetY), new(width, height))),
-                    guid, true, onMask, frame, keyFrameGuid ?? default),
-                new EndPasteImage_Action());
-        }*/
-
-        /*void AddMembers(Guid parentGuid, IEnumerable<DocumentViewModelBuilder.StructureMemberBuilder> builders)
-        {
-            foreach (var child in builders.Reverse())
-            {
-                if (child.Id == default)
-                {
-                    child.Id = Guid.NewGuid();
-                }
-
-                AddMember(parentGuid, child);
-            }
-        }*/
-
         void AddAnimationData(AnimationDataBuilder? data, Dictionary<int, Guid> mappedIds,
             Dictionary<int, Guid> mappedKeyFrameIds)
         {
@@ -516,6 +447,26 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                 }
             }
         }
+        
+        bool IsFileWithOldColorBlending((string serializerName, string serializerVersion) serializerData)
+        {
+            if(string.IsNullOrEmpty(serializerData.serializerName) && string.IsNullOrEmpty(serializerData.serializerVersion))
+            {
+                return true;
+            }
+
+            try
+            {
+                Version parsedVersion = new Version(serializerData.serializerVersion);
+
+                return serializerData.serializerName == "PixiEditor" 
+                       && parsedVersion is { Major: 2, Minor: 0, Build: 0, Revision: >= 28 and <= 31 };
+            }
+            catch (Exception)
+            {
+                return false;
+            }
+        }
     }
 
     public void MarkAsSaved()
@@ -807,6 +758,11 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         OnPropertyChanged(nameof(HorizontalSymmetryAxisYBindable));
     }
 
+    public void SetProcessingColorSpace(ColorSpace infoColorSpace)
+    {
+        UsesLegacyBlending = infoColorSpace.IsSrgb;
+    }
+
     public void SetSize(VecI size)
     {
         var oldSize = size;

+ 26 - 79
src/PixiEditor/Views/Rendering/Scene.cs

@@ -128,7 +128,10 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     private string info = string.Empty;
     private bool initialized = false;
     private RenderApiResources resources;
-    private DrawingSurface renderSurface;
+
+    private DrawingSurface framebuffer;
+    private Texture renderTexture;
+
     private PixelSize lastSize = PixelSize.Empty;
     private Cursor lastCursor;
 
@@ -249,13 +252,13 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     private void RenderScene(RectD bounds)
     {
-        DrawCheckerboard(bounds);
-        DrawOverlays(renderSurface, bounds, OverlayRenderSorting.Background);
-        SceneRenderer.RenderScene(renderSurface, CalculateResolution());
-        DrawOverlays(renderSurface, bounds, OverlayRenderSorting.Foreground);
+        DrawCheckerboard(renderTexture.DrawingSurface, bounds);
+        DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Background);
+        SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution());
+        DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Foreground);
     }
 
-    private void DrawCheckerboard(RectD dirtyBounds)
+    private void DrawCheckerboard(DrawingSurface surface, RectD dirtyBounds)
     {
         if (checkerBitmap == null) return;
 
@@ -271,7 +274,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             FilterQuality = FilterQuality.None
         };
 
-        renderSurface.Canvas.DrawRect(operationSurfaceRectToRender, checkerPaint);
+        surface.Canvas.DrawRect(operationSurfaceRectToRender, checkerPaint);
     }
 
     private void DrawOverlays(DrawingSurface renderSurface, RectD dirtyBounds, OverlayRenderSorting sorting)
@@ -584,8 +587,11 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     protected void FreeGraphicsResources()
     {
         resources?.DisposeAsync();
-        renderSurface?.Dispose();
-        renderSurface = null;
+        framebuffer?.Dispose();
+        framebuffer = null;
+
+        renderTexture?.Dispose();
+        renderTexture = null;
         resources = null;
     }
 
@@ -598,26 +604,31 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
                 return;
             }
 
-            if (renderSurface == null || lastSize != size)
+            if (framebuffer == null || lastSize != size)
             {
                 resources.CreateTemporalObjects(size);
 
                 VecI sizeVec = new VecI(size.Width, size.Height);
 
-                renderSurface?.Dispose();
+                framebuffer?.Dispose();
+                renderTexture?.Dispose();
 
-                renderSurface =
+                framebuffer =
                     DrawingBackendApi.Current.CreateRenderSurface(sizeVec,
                         resources.Texture, SurfaceOrigin.BottomLeft);
 
+                renderTexture = Texture.ForDisplay(sizeVec);
+
                 lastSize = size;
             }
 
             resources.Render(size, () =>
             {
-                renderSurface.Canvas.Clear();
-                Draw(renderSurface);
-                renderSurface.Flush();
+                framebuffer.Canvas.Clear();
+                renderTexture.DrawingSurface.Canvas.Clear();
+                Draw(renderTexture.DrawingSurface);
+                framebuffer.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
+                framebuffer.Flush();
             });
         }
     }
@@ -699,67 +710,3 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         return Bounds.Contains(point);
     }
 }
-
-internal class DrawSceneOperation : SkiaDrawOperation
-{
-    public DocumentViewModel Document { get; set; }
-    public VecD ContentPosition { get; set; }
-    public double Scale { get; set; }
-    public double ResolutionScale { get; set; }
-    public double Angle { get; set; }
-    public bool FlipX { get; set; }
-    public bool FlipY { get; set; }
-    public Rect ViewportBounds { get; }
-
-
-    public Action<DrawingSurface> RenderScene;
-
-    private Texture renderTexture;
-
-    private double opacity;
-
-    public DrawSceneOperation(Action<DrawingSurface> renderAction, DocumentViewModel document, VecD contentPosition,
-        double scale,
-        double resolutionScale,
-        double opacity,
-        double angle, bool flipX, bool flipY, Rect dirtyBounds, Rect viewportBounds,
-        Texture renderTexture) : base(dirtyBounds)
-    {
-        RenderScene = renderAction;
-        Document = document;
-        ContentPosition = contentPosition;
-        Scale = scale;
-        Angle = angle;
-        FlipX = flipX;
-        FlipY = flipY;
-        ViewportBounds = viewportBounds;
-        ResolutionScale = resolutionScale;
-        this.opacity = opacity;
-        this.renderTexture = renderTexture;
-    }
-
-    public override void Render(ISkiaSharpApiLease lease)
-    {
-        if (Document == null) return;
-
-        SKCanvas canvas = lease.SkCanvas;
-
-        int count = canvas.Save();
-
-        //using var ctx = DrawingBackendApi.Current.RenderOnDifferentGrContext(lease.GrContext);
-
-        DrawingSurface surface = DrawingSurface.FromNative(lease.SkSurface);
-
-        surface.Canvas.DrawSurface(renderTexture.DrawingSurface, 0, 0);
-
-        RenderScene?.Invoke(surface);
-
-        canvas.RestoreToCount(count);
-        DrawingSurface.Unmanage(surface);
-    }
-
-    public override bool Equals(ICustomDrawOperation? other)
-    {
-        return false;
-    }
-}

+ 1 - 2
src/PixiEditor/Views/Visuals/PreviewPainterControl.cs

@@ -79,8 +79,7 @@ public class PreviewPainterControl : DrawieControl
             UniformScale(x, y, surface, previewBounds.Value);
         }
 
-        // TODO: Implement ChunkResolution and frame
-        PreviewPainter.Paint(surface, ChunkResolution.Full, FrameToRender);
+        PreviewPainter.Paint(surface);
 
         surface.Canvas.Restore();
     }

+ 1 - 1
src/PixiParser

@@ -1 +1 @@
-Subproject commit 64b02fab35d2f7fe30a8426b49ff59e1e37ed452
+Subproject commit 4176e1eb211e5e6f8932671e82b2813c737a1a56

+ 2 - 1
tests/ChunkyImageLibTest/ChunkyImageTests.cs

@@ -3,6 +3,7 @@ using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 using Drawie.Skia;
 using Xunit;
@@ -24,7 +25,7 @@ public class ChunkyImageTests
     {
         ChunkyImage image = new ChunkyImage(new VecI(ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize));
         image.EnqueueDrawRectangle(new(new(5, 5), new(80, 80), 0, 2, Colors.AliceBlue, Colors.Snow));
-        using (Chunk target = Chunk.Create())
+        using (Chunk target = Chunk.Create(ColorSpace.CreateSrgb()))
         {
             image.DrawMostUpToDateChunkOn(new(0, 0), ChunkResolution.Full, target.Surface.DrawingSurface, VecI.Zero);
             image.CancelChanges();

+ 1 - 0
tests/PixiEditor.Backend.Tests/MockDocument.cs

@@ -74,4 +74,5 @@ public class MockDocument : IReadOnlyDocument
 
     public IReadOnlyReferenceLayer? ReferenceLayer { get; }
     public DocumentRenderer Renderer { get; }
+    public ColorSpace ProcessingColorSpace { get; }
 }