Browse Source

Make things smoooth

Equbuxu 3 years ago
parent
commit
e09908f4da

+ 8 - 7
src/ChunkyImageLib/ChunkyImage.cs

@@ -54,6 +54,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private static SKPaint ClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstIn };
     private static SKPaint ClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstIn };
     private static SKPaint InverseClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstOut };
     private static SKPaint InverseClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstOut };
     private static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
     private static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
+    private static SKPaint SmoothReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src, FilterQuality = SKFilterQuality.Medium };
     private static SKPaint AddingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Plus };
     private static SKPaint AddingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Plus };
     private SKPaint blendModePaint = new SKPaint() { BlendMode = SKBlendMode.Src };
     private SKPaint blendModePaint = new SKPaint() { BlendMode = SKBlendMode.Src };
 
 
@@ -375,33 +376,33 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
     }
 
 
     /// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
     /// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
-    public void EnqueueDrawPath(SKPath path, SKColor color, float strokeWidth, SKStrokeCap strokeCap, RectI? customBounds = null)
+    public void EnqueueDrawPath(SKPath path, SKColor color, float strokeWidth, SKStrokeCap strokeCap, SKBlendMode blendMode, RectI? customBounds = null)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
             ThrowIfDisposed();
             ThrowIfDisposed();
-            PathOperation operation = new(path, color, strokeWidth, strokeCap, customBounds);
+            PathOperation operation = new(path, color, strokeWidth, strokeCap, blendMode, customBounds);
             EnqueueOperation(operation);
             EnqueueOperation(operation);
         }
         }
     }
     }
 
 
-    public void EnqueueDrawBresenhamLine(VecI from, VecI to, SKColor color)
+    public void EnqueueDrawBresenhamLine(VecI from, VecI to, SKColor color, SKBlendMode blendMode)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
             ThrowIfDisposed();
             ThrowIfDisposed();
-            BresenhamLineOperation operation = new(from, to, color);
+            BresenhamLineOperation operation = new(from, to, color, blendMode);
             EnqueueOperation(operation);
             EnqueueOperation(operation);
         }
         }
     }
     }
 
 
 
 
-    public void EnqueueDrawSkiaLine(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color)
+    public void EnqueueDrawSkiaLine(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color, SKBlendMode blendMode)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
             ThrowIfDisposed();
             ThrowIfDisposed();
-            SkiaLineOperation operation = new(from, to, strokeCap, strokeWidth, color);
+            SkiaLineOperation operation = new(from, to, strokeCap, strokeWidth, color, blendMode);
             EnqueueOperation(operation);
             EnqueueOperation(operation);
         }
         }
     }
     }
@@ -912,7 +913,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             newChunk.Surface.SkiaSurface.Canvas.Save();
             newChunk.Surface.SkiaSurface.Canvas.Save();
             newChunk.Surface.SkiaSurface.Canvas.Scale((float)resolution.Multiplier());
             newChunk.Surface.SkiaSurface.Canvas.Scale((float)resolution.Multiplier());
 
 
-            newChunk.Surface.SkiaSurface.Canvas.DrawSurface(existingFullResChunk!.Surface.SkiaSurface, 0, 0, ReplacingPaint);
+            newChunk.Surface.SkiaSurface.Canvas.DrawSurface(existingFullResChunk!.Surface.SkiaSurface, 0, 0, SmoothReplacingPaint);
             newChunk.Surface.SkiaSurface.Canvas.Restore();
             newChunk.Surface.SkiaSurface.Canvas.Restore();
             committedChunks[resolution][chunkPos] = newChunk;
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
             return newChunk;

+ 10 - 3
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -7,19 +7,26 @@ internal class BresenhamLineOperation : IDrawOperation
     public bool IgnoreEmptyChunks => false;
     public bool IgnoreEmptyChunks => false;
     private readonly VecI from;
     private readonly VecI from;
     private readonly VecI to;
     private readonly VecI to;
+    private readonly SKColor color;
+    private readonly SKBlendMode blendMode;
     private readonly SKPoint[] points;
     private readonly SKPoint[] points;
     private SKPaint paint;
     private SKPaint paint;
 
 
-    public BresenhamLineOperation(VecI from, VecI to, SKColor color)
+    public BresenhamLineOperation(VecI from, VecI to, SKColor color, SKBlendMode blendMode)
     {
     {
         this.from = from;
         this.from = from;
         this.to = to;
         this.to = to;
-        paint = new SKPaint() { Color = color };
+        this.color = color;
+        this.blendMode = blendMode;
+        paint = new SKPaint() { BlendMode = blendMode };
         points = BresenhamLineHelper.GetBresenhamLine(from, to);
         points = BresenhamLineHelper.GetBresenhamLine(from, to);
     }
     }
 
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
     {
+        // a hacky way to make the lines look slightly better on non full res chunks
+        paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
+
         var surf = chunk.Surface.SkiaSurface;
         var surf = chunk.Surface.SkiaSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
@@ -48,7 +55,7 @@ internal class BresenhamLineOperation : IDrawOperation
             newFrom = newFrom.ReflectY((int)horAxisY);
             newFrom = newFrom.ReflectY((int)horAxisY);
             newTo = newTo.ReflectY((int)horAxisY);
             newTo = newTo.ReflectY((int)horAxisY);
         }
         }
-        return new BresenhamLineOperation(newFrom, newTo, paint.Color);
+        return new BresenhamLineOperation(newFrom, newTo, color, blendMode);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 2 - 0
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -57,6 +57,8 @@ internal class EllipseOperation : IDrawOperation
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
 
 
+        paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
+
         if (strokeWidth == 1)
         if (strokeWidth == 1)
         {
         {
             if (fillColor.Alpha > 0)
             if (fillColor.Alpha > 0)

+ 1 - 0
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -55,6 +55,7 @@ internal class ImageOperation : IDrawOperation
 
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
     {
+        //customPaint.FilterQuality = chunk.Resolution != ChunkResolution.Full;
         float scaleMult = (float)chunk.Resolution.Multiplier();
         float scaleMult = (float)chunk.Resolution.Multiplier();
         VecD trans = -chunkPos * ChunkPool.FullChunkSize;
         VecD trans = -chunkPos * ChunkPool.FullChunkSize;
 
 

+ 4 - 3
src/ChunkyImageLib/Operations/PathOperation.cs

@@ -11,10 +11,10 @@ internal class PathOperation : IDrawOperation
 
 
     public bool IgnoreEmptyChunks => false;
     public bool IgnoreEmptyChunks => false;
 
 
-    public PathOperation(SKPath path, SKColor color, float strokeWidth, SKStrokeCap cap, RectI? customBounds = null)
+    public PathOperation(SKPath path, SKColor color, float strokeWidth, SKStrokeCap cap, SKBlendMode blendMode, RectI? customBounds = null)
     {
     {
         this.path = new SKPath(path);
         this.path = new SKPath(path);
-        paint = new() { Color = color, Style = SKPaintStyle.Stroke, StrokeWidth = strokeWidth, StrokeCap = cap };
+        paint = new() { Color = color, Style = SKPaintStyle.Stroke, StrokeWidth = strokeWidth, StrokeCap = cap, BlendMode = blendMode };
 
 
         RectI floatBounds = customBounds ?? (RectI)((RectD)path.TightBounds).RoundOutwards();
         RectI floatBounds = customBounds ?? (RectI)((RectD)path.TightBounds).RoundOutwards();
         bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
         bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
@@ -22,6 +22,7 @@ internal class PathOperation : IDrawOperation
 
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
     {
+        paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
         var surf = chunk.Surface.SkiaSurface;
         var surf = chunk.Surface.SkiaSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
@@ -46,7 +47,7 @@ internal class PathOperation : IDrawOperation
             newBounds = newBounds.ReflectX((int)verAxisX);
             newBounds = newBounds.ReflectX((int)verAxisX);
         if (horAxisY is not null)
         if (horAxisY is not null)
             newBounds = newBounds.ReflectY((int)horAxisY);
             newBounds = newBounds.ReflectY((int)horAxisY);
-        return new PathOperation(copy, paint.Color, paint.StrokeWidth, paint.StrokeCap, newBounds);
+        return new PathOperation(copy, paint.Color, paint.StrokeWidth, paint.StrokeCap, paint.BlendMode, newBounds);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 4 - 2
src/ChunkyImageLib/Operations/SkiaLineOperation.cs

@@ -10,7 +10,7 @@ internal class SkiaLineOperation : IDrawOperation
     private readonly VecI from;
     private readonly VecI from;
     private readonly VecI to;
     private readonly VecI to;
 
 
-    public SkiaLineOperation(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color)
+    public SkiaLineOperation(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color, SKBlendMode blendMode)
     {
     {
         paint = new()
         paint = new()
         {
         {
@@ -18,6 +18,7 @@ internal class SkiaLineOperation : IDrawOperation
             StrokeWidth = strokeWidth,
             StrokeWidth = strokeWidth,
             Color = color,
             Color = color,
             Style = SKPaintStyle.Stroke,
             Style = SKPaintStyle.Stroke,
+            BlendMode = blendMode,
         };
         };
         this.from = from;
         this.from = from;
         this.to = to;
         this.to = to;
@@ -25,6 +26,7 @@ internal class SkiaLineOperation : IDrawOperation
 
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
     {
+        paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
         var surf = chunk.Surface.SkiaSurface;
         var surf = chunk.Surface.SkiaSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
@@ -53,7 +55,7 @@ internal class SkiaLineOperation : IDrawOperation
             newFrom = newFrom.ReflectY((int)horAxisY);
             newFrom = newFrom.ReflectY((int)horAxisY);
             newTo = newFrom.ReflectY((int)horAxisY);
             newTo = newFrom.ReflectY((int)horAxisY);
         }
         }
-        return new SkiaLineOperation(newFrom, newTo, paint.StrokeCap, paint.StrokeWidth, paint.Color);
+        return new SkiaLineOperation(newFrom, newTo, paint.StrokeCap, paint.StrokeWidth, paint.Color, paint.BlendMode);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 8 - 6
src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs

@@ -36,6 +36,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         if (strokeWidth < 1)
         if (strokeWidth < 1)
             return new Error();
             return new Error();
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+        image.SetBlendMode(SKBlendMode.SrcOver);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
         return new Success();
         return new Success();
     }
     }
@@ -49,9 +50,9 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         int opCount = image.QueueLength;
         int opCount = image.QueueLength;
 
 
         if (strokeWidth == 1)
         if (strokeWidth == 1)
-            image.EnqueueDrawBresenhamLine(from, to, color);
+            image.EnqueueDrawBresenhamLine(from, to, color, SKBlendMode.Src);
         else
         else
-            image.EnqueueDrawSkiaLine(from, to, SKStrokeCap.Round, strokeWidth, color);
+            image.EnqueueDrawSkiaLine(from, to, SKStrokeCap.Round, strokeWidth, color, SKBlendMode.Src);
         var affChunks = image.FindAffectedChunks(opCount);
         var affChunks = image.FindAffectedChunks(opCount);
 
 
         return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);
         return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);
@@ -62,17 +63,17 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         if (points.Count == 1)
         if (points.Count == 1)
         {
         {
             if (strokeWidth == 1)
             if (strokeWidth == 1)
-                targetImage.EnqueueDrawBresenhamLine(points[0], points[0], color);
+                targetImage.EnqueueDrawBresenhamLine(points[0], points[0], color, SKBlendMode.Src);
             else
             else
-                targetImage.EnqueueDrawSkiaLine(points[0], points[0], SKStrokeCap.Round, strokeWidth, color);
+                targetImage.EnqueueDrawSkiaLine(points[0], points[0], SKStrokeCap.Round, strokeWidth, color, SKBlendMode.Src);
             return;
             return;
         }
         }
         for (int i = 1; i < points.Count; i++)
         for (int i = 1; i < points.Count; i++)
         {
         {
             if (strokeWidth == 1)
             if (strokeWidth == 1)
-                targetImage.EnqueueDrawBresenhamLine(points[i - 1], points[i], color);
+                targetImage.EnqueueDrawBresenhamLine(points[i - 1], points[i], color, SKBlendMode.Src);
             else
             else
-                targetImage.EnqueueDrawSkiaLine(points[i - 1], points[i], SKStrokeCap.Round, strokeWidth, color);
+                targetImage.EnqueueDrawSkiaLine(points[i - 1], points[i], SKStrokeCap.Round, strokeWidth, color, SKBlendMode.Src);
         }
         }
     }
     }
 
 
@@ -95,6 +96,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         }
         }
         else
         else
         {
         {
+            image.SetBlendMode(SKBlendMode.SrcOver);
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
 
 
             FastforwardEnqueueDrawLines(image);
             FastforwardEnqueueDrawLines(image);

+ 5 - 3
src/PixiEditor.ChangeableDocument/Changes/Drawing/PathBasedPen_UpdateableChange.cs

@@ -36,6 +36,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         if (!DrawingChangeHelper.IsValidForDrawing(target, memberGuid, drawOnMask))
         if (!DrawingChangeHelper.IsValidForDrawing(target, memberGuid, drawOnMask))
             return new Error();
             return new Error();
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
+        image.SetBlendMode(SKBlendMode.SrcOver);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
         return new Success();
         return new Success();
     }
     }
@@ -53,7 +54,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         for (int i = 0; i < points.Count; i++)
         for (int i = 0; i < points.Count; i++)
         {
         {
             UpdateTempPath(i + 1);
             UpdateTempPath(i + 1);
-            image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round);
+            image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round, SKBlendMode.Src);
         }
         }
     }
     }
 
 
@@ -111,7 +112,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
             firstApply = false;
             firstApply = false;
             UpdateTempPathFinish();
             UpdateTempPathFinish();
 
 
-            image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round);
+            image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round, SKBlendMode.Src);
             var affChunks = image.FindAffectedChunks();
             var affChunks = image.FindAffectedChunks();
             storedChunks = new CommittedChunkStorage(image, affChunks);
             storedChunks = new CommittedChunkStorage(image, affChunks);
             image.CommitChanges();
             image.CommitChanges();
@@ -120,6 +121,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         }
         }
         else
         else
         {
         {
+            image.SetBlendMode(SKBlendMode.SrcOver);
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
 
 
             FastforwardEnqueueDrawPath(image);
             FastforwardEnqueueDrawPath(image);
@@ -137,7 +139,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
 
 
         int opCount = image.QueueLength;
         int opCount = image.QueueLength;
-        image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round);
+        image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round, SKBlendMode.Src);
         var affChunks = image.FindAffectedChunks(opCount);
         var affChunks = image.FindAffectedChunks(opCount);
 
 
         return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);
         return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);

+ 25 - 0
src/PixiEditorPrototype/Converters/ScaleToBitmapScalingModeConverter.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace PixiEditorPrototype.Converters
+{
+    internal class ScaleToBitmapScalingModeConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is not double scale)
+                return DependencyProperty.UnsetValue;
+            if (scale < 1)
+                return BitmapScalingMode.HighQuality;
+            return BitmapScalingMode.NearestNeighbor;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 4 - 3
src/PixiEditorPrototype/Models/Rendering/WriteableBitmapUpdater.cs

@@ -20,6 +20,7 @@ internal class WriteableBitmapUpdater
 
 
     private static readonly SKPaint BlendingPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
     private static readonly SKPaint BlendingPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
     private static readonly SKPaint ReplacingPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
     private static readonly SKPaint ReplacingPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
+    private static readonly SKPaint SmoothReplacingPaint = new SKPaint() { BlendMode = SKBlendMode.Src, FilterQuality = SKFilterQuality.Medium, IsAntialias = true };
     private static readonly SKPaint SelectionPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver, Color = new(0xa0FFFFFF) };
     private static readonly SKPaint SelectionPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver, Color = new(0xa0FFFFFF) };
     private static readonly SKPaint ClearPaint = new SKPaint() { BlendMode = SKBlendMode.Src, Color = SKColors.Transparent };
     private static readonly SKPaint ClearPaint = new SKPaint() { BlendMode = SKBlendMode.Src, Color = SKColors.Transparent };
 
 
@@ -137,7 +138,7 @@ internal class WriteableBitmapUpdater
                     var pos = chunk * ChunkResolution.Full.PixelSize();
                     var pos = chunk * ChunkResolution.Full.PixelSize();
                     // the full res chunks are already rendered so drawing them again should be fast
                     // the full res chunks are already rendered so drawing them again should be fast
                     if (!layer.LayerImage.DrawMostUpToDateChunkOn
                     if (!layer.LayerImage.DrawMostUpToDateChunkOn
-                        (chunk, ChunkResolution.Full, memberVM.PreviewSurface, pos, ReplacingPaint))
+                        (chunk, ChunkResolution.Full, memberVM.PreviewSurface, pos, SmoothReplacingPaint))
                         memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize, ClearPaint);
                         memberVM.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize, ClearPaint);
                 }
                 }
                 infos.Add(new PreviewDirty_RenderInfo(guid));
                 infos.Add(new PreviewDirty_RenderInfo(guid));
@@ -153,7 +154,7 @@ internal class WriteableBitmapUpdater
                     OneOf<Chunk, EmptyChunk> rendered = ChunkRenderer.MergeWholeStructure(chunk, ChunkResolution.Full, folder);
                     OneOf<Chunk, EmptyChunk> rendered = ChunkRenderer.MergeWholeStructure(chunk, ChunkResolution.Full, folder);
                     if (rendered.IsT0)
                     if (rendered.IsT0)
                     {
                     {
-                        memberVM.PreviewSurface.Canvas.DrawSurface(rendered.AsT0.Surface.SkiaSurface, pos, ReplacingPaint);
+                        memberVM.PreviewSurface.Canvas.DrawSurface(rendered.AsT0.Surface.SkiaSurface, pos, SmoothReplacingPaint);
                         rendered.AsT0.Dispose();
                         rendered.AsT0.Dispose();
                     }
                     }
                     else
                     else
@@ -183,7 +184,7 @@ internal class WriteableBitmapUpdater
             {
             {
                 var pos = chunk * ChunkResolution.Full.PixelSize();
                 var pos = chunk * ChunkResolution.Full.PixelSize();
                 member.Mask!.DrawMostUpToDateChunkOn
                 member.Mask!.DrawMostUpToDateChunkOn
-                    (chunk, ChunkResolution.Full, memberVM.MaskPreviewSurface, pos, ReplacingPaint);
+                    (chunk, ChunkResolution.Full, memberVM.MaskPreviewSurface, pos, SmoothReplacingPaint);
             }
             }
 
 
             memberVM.MaskPreviewSurface.Canvas.Restore();
             memberVM.MaskPreviewSurface.Canvas.Restore();

+ 2 - 1
src/PixiEditorPrototype/UserControls/Viewport/Viewport.xaml

@@ -16,6 +16,7 @@
              d:DesignHeight="450" d:DesignWidth="800">
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
     <UserControl.Resources>
         <conv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
         <conv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
+        <conv:ScaleToBitmapScalingModeConverter x:Key="ScaleToBitmapScalingModeConverter"/>
     </UserControl.Resources>
     </UserControl.Resources>
     <Grid>
     <Grid>
         <zoombox:Zoombox 
         <zoombox:Zoombox 
@@ -35,7 +36,7 @@
                         Focusable="True"
                         Focusable="True"
                         Width="{Binding Document.Width}" Height="{Binding Document.Height}"
                         Width="{Binding Document.Width}" Height="{Binding Document.Height}"
                         Source="{Binding TargetBitmap}"
                         Source="{Binding TargetBitmap}"
-                        RenderOptions.BitmapScalingMode="NearestNeighbor">
+                        RenderOptions.BitmapScalingMode="{Binding Zoombox.Scale, Converter={StaticResource ScaleToBitmapScalingModeConverter}}">
                         <i:Interaction.Triggers>
                         <i:Interaction.Triggers>
                             <i:EventTrigger EventName="MouseDown">
                             <i:EventTrigger EventName="MouseDown">
                                 <i:InvokeCommandAction Command="{Binding MouseDownCommand}" PassEventArgsToCommand="True"/>
                                 <i:InvokeCommandAction Command="{Binding MouseDownCommand}" PassEventArgsToCommand="True"/>