Explorar o código

Merge branch 'master' into brush-engine

Krzysztof Krysiński hai 2 meses
pai
achega
621cb93f7e
Modificáronse 100 ficheiros con 3202 adicións e 1313 borrados
  1. BIN=BIN
      assets/flatpak/icon-512.png
  2. 14 0
      assets/flatpak/net.pixieditor.PixiEditor-mime.xml
  3. 13 0
      assets/flatpak/net.pixieditor.PixiEditor.desktop
  4. 87 0
      assets/flatpak/net.pixieditor.PixiEditor.metainfo.xml
  5. BIN=BIN
      assets/flatpak/screenshots/anim.png
  6. BIN=BIN
      assets/flatpak/screenshots/graph.png
  7. BIN=BIN
      assets/flatpak/screenshots/palettes.png
  8. BIN=BIN
      assets/flatpak/screenshots/vector.png
  9. 1 1
      samples/Directory.Build.props
  10. 19 8
      src/ChunkyImageLib/Chunk.cs
  11. 96 5
      src/ChunkyImageLib/ChunkyImage.cs
  12. 88 9
      src/ChunkyImageLib/ChunkyImageEx.cs
  13. 1 1
      src/ChunkyImageLib/CommittedChunkStorage.cs
  14. 27 25
      src/ChunkyImageLib/DataHolders/ColorBounds.cs
  15. 1 0
      src/ChunkyImageLib/IReadOnlyChunkyImage.cs
  16. 1 2
      src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs
  17. 28 19
      src/ChunkyImageLib/Operations/EllipseOperation.cs
  18. 7 3
      src/ChunkyImageLib/Operations/ImageOperation.cs
  19. 86 24
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  20. 1 1
      src/ColorPicker
  21. 0 81
      src/Custom.ruleset
  22. 3 9
      src/Directory.Build.props
  23. 1 1
      src/Drawie
  24. 1 1
      src/PixiDocks
  25. 8 14
      src/PixiEditor.AnimationRenderer.FFmpeg/FFMpegRenderer.cs
  26. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameData.cs
  27. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  28. 10 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Filter.cs
  29. 0 13
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IPreviewRenderable.cs
  30. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyLayerNode.cs
  31. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs
  32. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyStructureNode.cs
  33. 12 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs
  34. 71 20
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  35. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CacheTriggerFlags.cs
  36. 11 11
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/CombineChannelsNode.cs
  37. 2 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CombineSeparate/SeparateChannelsNode.cs
  38. 29 35
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  39. 3 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs
  40. 155 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/PosterizationNode.cs
  41. 9 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FilterNodes/ApplyFilterNode.cs
  42. 20 21
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  43. 65 83
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs
  44. 6 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  45. 15 9
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/Matrix3X3BaseNode.cs
  46. 4 31
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs
  47. 25 8
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageLeftNode.cs
  48. 7 13
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ModifyImageRightNode.cs
  49. 12 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs
  50. 3 9
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/NoiseNode.cs
  51. 19 24
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  52. 57 12
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs
  53. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/SampleImageNode.cs
  54. 4 9
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs
  55. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs
  56. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs
  57. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs
  58. 8 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/RasterizeShapeNode.cs
  59. 59 75
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  60. 56 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Text/SliceTextNode.cs
  61. 51 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Text/TextIndexOfNode.cs
  62. 39 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Text/TextInfoNode.cs
  63. 0 11
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TileNode.cs
  64. 25 17
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/VectorLayerNode.cs
  65. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Workspace/CustomOutputNode.cs
  66. 77 0
      src/PixiEditor.ChangeableDocument/Changes/Animation/CreateAnimationDataFromFolder_Change.cs
  67. 4 1
      src/PixiEditor.ChangeableDocument/Changes/Animation/CreateAnimationDataFromLayer_Change.cs
  68. 28 11
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  69. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFill_Change.cs
  70. 5 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelected_UpdateableChange.cs
  71. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs
  72. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/Crop_Change.cs
  73. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs
  74. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs
  75. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs
  76. 6 4
      src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand/MagicWandHelper.cs
  77. 62 14
      src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateFolder_Change.cs
  78. 2 1
      src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs
  79. 0 17
      src/PixiEditor.ChangeableDocument/Helpers/PreviewUtils.cs
  80. 1 118
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  81. 43 0
      src/PixiEditor.ChangeableDocument/Rendering/PreviewRenderRequest.cs
  82. 48 0
      src/PixiEditor.ChangeableDocument/Rendering/PreviewUtility.cs
  83. 18 2
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  84. 6 12
      src/PixiEditor.Extensions.CommonApi/PixiEditor.Extensions.CommonApi.csproj
  85. 2 0
      src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs
  86. 1 1
      src/PixiEditor.Extensions/UI/Overlays/IHandle.cs
  87. 28 31
      src/PixiEditor.PixiAuth/PixiAuthClient.cs
  88. 8 1
      src/PixiEditor.UI.Common/Controls/TextBox.axaml
  89. BIN=BIN
      src/PixiEditor.UI.Common/Fonts/PixiPerfect.ttf
  90. 3 0
      src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml
  91. 3 0
      src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml.cs
  92. 2 2
      src/PixiEditor.UI.Common/Fonts/defs.svg
  93. 0 1
      src/PixiEditor.sln
  94. 18 3
      src/PixiEditor/Data/Configs/ToolSetsConfig.json
  95. 663 216
      src/PixiEditor/Data/Localization/Languages/ar.json
  96. 0 28
      src/PixiEditor/Data/Localization/Languages/cs.json
  97. 0 26
      src/PixiEditor/Data/Localization/Languages/de.json
  98. 33 1
      src/PixiEditor/Data/Localization/Languages/en.json
  99. 160 165
      src/PixiEditor/Data/Localization/Languages/es.json
  100. 691 0
      src/PixiEditor/Data/Localization/Languages/fr.json

BIN=BIN
assets/flatpak/icon-512.png


+ 14 - 0
assets/flatpak/net.pixieditor.PixiEditor-mime.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+SPDX-License-Identifier: GPL-3.0-or-later
+-->
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+
+  <!-- Native Pixi format -->
+  <mime-type type="application/x-pixi">
+    <comment>Pixi Project File</comment>
+    <generic-icon name="application-x-pixi"/>
+    <glob pattern="*.pixi"/>
+  </mime-type>
+</mime-info>
+

+ 13 - 0
assets/flatpak/net.pixieditor.PixiEditor.desktop

@@ -0,0 +1,13 @@
+[Desktop Entry]
+Name=PixiEditor
+Comment=PixiEditor is all-in-one solution for 2D image editing.
+Icon=net.pixieditor.PixiEditor
+Exec=pixieditor.sh %u
+StartupWMClass=pixieditor
+Terminal=false
+Type=Application
+Categories=Graphics;2DGraphics;RasterGraphics;VectorGraphics
+MimeType=application/x-pixi;image/jpeg;image/png;image/gif;image/bmp;image/webp;image/svg+xml;font/otf;font/ttf;x-scheme-handler/lospec-palette;
+GenericName=2D Editor
+SingleMainWindow=true
+Keywords=editor;image;2d;graphics;design;vector;raster;

+ 87 - 0
assets/flatpak/net.pixieditor.PixiEditor.metainfo.xml

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2025 Krzysztof Krysiński -->
+<component type="desktop-application">
+  <id>net.pixieditor.PixiEditor</id>
+  
+  <name>PixiEditor</name>
+  <summary>Universal node-based 2D editor</summary>
+  
+  <metadata_license>CC-BY-SA-4.0</metadata_license>
+  <project_license>LGPL-3.0-or-later</project_license>
+
+  <developer id="net.pixieditor">
+    <name>PixiEditor</name>
+  </developer>
+
+  <requires>
+    <control>keyboard</control>
+    <control>pointing</control>
+    <display_length compare="ge">768</display_length>
+  </requires>
+
+  <url type="homepage">https://pixieditor.net</url>
+  <url type="bugtracker">https://github.com/PixiEditor/PixiEditor/issues</url>
+  <url type="donation">https://pixieditor.net/download</url>
+  <url type="contact">https://pixieditor.net/help</url>
+  <url type="faq">https://pixieditor.net/docs/faq</url>
+  <url type="contribute">https://github.com/PixiEditor/PixiEditor</url>
+  <url type="vcs-browser">https://github.com/PixiEditor/PixiEditor</url>
+
+  <icon type="stock">net.pixieditor.PixiEditor</icon>
+
+  <branding>
+    <color type="primary" scheme_preference="light">#dedede</color>
+    <color type="primary" scheme_preference="dark">#1a1a1a</color>
+  </branding>
+
+  <content_rating type="oars-1.1" />
+
+  <releases>
+    <release version="2.0.1.16" date="2025-10-06">
+      <url type="details">https://forum.pixieditor.net/t/changelog-2-0-1-16/461</url>
+      <description>
+        <p>Posterize and Text Nodes, transforming improvements and new languagesW</p>
+      </description>      
+    </release>
+    <release version="2.0.1.14" date="2025-09-10">
+      <url type="details">https://forum.pixieditor.net/t/changelog-2-0-1-14/439/1</url>
+      <description>
+        <p>Fixed rectangle rendering issues</p>
+      </description>      
+    </release>
+  </releases>
+  
+  <description>
+    <p>
+	PixiEditor is a universal 2D editor designed for all kinds of creative work. Whether you want to make game sprites, paint illustrations, design logos, edit images, or create animations, PixiEditor gives you the tools to bring your ideas to life—all in a clean and familiar interface.
+    </p>
+    <p>
+	Powered by a Node Graph for advanced, non-destructive editing, PixiEditor comes with three unique toolsets that can be used together on the same canvas:
+    </p>
+    <p>
+	Pixel Art – pixel-perfect drawing tools for sprites and retro graphics
+	Painting – soft brushes, smooth lines, and anti-aliased shapes
+		    Vector – scalable paths and shapes for logos and clean designs
+    </p>
+  </description>
+  
+  <launchable type="desktop-id">net.pixieditor.PixiEditor.desktop</launchable>
+  <screenshots>
+    <screenshot type="default">
+	    <image>https://raw.githubusercontent.com/pixieditor/pixieditor/refs/heads/flatpak/assets/flatpak/screenshots/anim.png</image>
+	    <caption>Frame-by-frame animation with onion skinning</caption>
+    </screenshot>
+    <screenshot>
+	    <image>https://raw.githubusercontent.com/pixieditor/pixieditor/refs/heads/flatpak/assets/flatpak/screenshots/graph.png</image>
+	    <caption>Procedurally generated islands</caption>
+    </screenshot>
+    <screenshot>
+	    <image>https://raw.githubusercontent.com/pixieditor/pixieditor/refs/heads/flatpak/assets/flatpak/screenshots/palettes.png</image>
+	    <caption>Palette browser</caption>
+    </screenshot>
+    <screenshot>
+	    <image>https://raw.githubusercontent.com/pixieditor/pixieditor/refs/heads/flatpak/assets/flatpak/screenshots/vector.png</image>
+	    <caption>Vectors editing tools</caption>
+    </screenshot>
+  </screenshots>
+</component>

BIN=BIN
assets/flatpak/screenshots/anim.png


BIN=BIN
assets/flatpak/screenshots/graph.png


BIN=BIN
assets/flatpak/screenshots/palettes.png


BIN=BIN
assets/flatpak/screenshots/vector.png


+ 1 - 1
samples/Directory.Build.props

@@ -1,7 +1,7 @@
 <Project>
 <Project>
     <PropertyGroup>
     <PropertyGroup>
         <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
         <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
-		    <AvaloniaVersion>11.3.0</AvaloniaVersion>
+		    <AvaloniaVersion>11.3.6</AvaloniaVersion>
     </PropertyGroup>
     </PropertyGroup>
     <ItemGroup>
     <ItemGroup>
         <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
         <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />

+ 19 - 8
src/ChunkyImageLib/Chunk.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
@@ -23,7 +24,7 @@ public class Chunk : IDisposable
     /// <summary>
     /// <summary>
     /// The surface of the chunk
     /// The surface of the chunk
     /// </summary>
     /// </summary>
-    public Surface Surface
+    public Texture Surface
     {
     {
         get
         get
         {
         {
@@ -50,7 +51,7 @@ public class Chunk : IDisposable
 
 
     public bool Disposed => returned;
     public bool Disposed => returned;
 
 
-    private Surface internalSurface;
+    private Texture internalSurface;
 
 
     private Chunk(ChunkResolution resolution, ColorSpace colorSpace)
     private Chunk(ChunkResolution resolution, ColorSpace colorSpace)
     {
     {
@@ -59,7 +60,7 @@ public class Chunk : IDisposable
         Resolution = resolution;
         Resolution = resolution;
         ColorSpace = colorSpace;
         ColorSpace = colorSpace;
         PixelSize = new(size, size);
         PixelSize = new(size, size);
-        internalSurface = new Surface(new ImageInfo(size, size, ColorType.RgbaF16, AlphaType.Premul, colorSpace));
+        internalSurface = new Texture(new ImageInfo(size, size, ColorType.RgbaF16, AlphaType.Premul, colorSpace) { GpuBacked = true });
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -67,7 +68,10 @@ public class Chunk : IDisposable
     /// </summary>
     /// </summary>
     public static Chunk Create(ColorSpace chunkCs, ChunkResolution resolution = ChunkResolution.Full)
     public static Chunk Create(ColorSpace chunkCs, ChunkResolution resolution = ChunkResolution.Full)
     {
     {
-        var chunk = ChunkPool.Instance.Get(resolution, chunkCs);
+        return new Chunk(resolution, chunkCs);
+
+        // Leaving this in case chunk pooling turns out to be better
+        /*var chunk = ChunkPool.Instance.Get(resolution, chunkCs);
         if (chunk == null || chunk.Disposed)
         if (chunk == null || chunk.Disposed)
         {
         {
             chunk = new Chunk(resolution, chunkCs);
             chunk = new Chunk(resolution, chunkCs);
@@ -75,7 +79,7 @@ public class Chunk : IDisposable
 
 
         chunk.returned = false;
         chunk.returned = false;
         Interlocked.Increment(ref chunkCounter);
         Interlocked.Increment(ref chunkCounter);
-        return chunk;
+        return chunk;*/
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -85,6 +89,7 @@ public class Chunk : IDisposable
     /// <param name="paint">The paint to use while drawing</param>
     /// <param name="paint">The paint to use while drawing</param>
     public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
     public void DrawChunkOn(DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
     {
     {
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
         if (samplingOptions == null || samplingOptions == SamplingOptions.Default)
         if (samplingOptions == null || samplingOptions == SamplingOptions.Default)
         {
         {
             surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
             surface.Canvas.DrawSurface(Surface.DrawingSurface, (float)pos.X, (float)pos.Y, paint);
@@ -98,6 +103,7 @@ public class Chunk : IDisposable
 
 
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)
     public unsafe RectI? FindPreciseBounds(RectI? passedSearchRegion = null)
     {
     {
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
         RectI? bounds = null;
         RectI? bounds = null;
         if (returned)
         if (returned)
             return bounds;
             return bounds;
@@ -109,7 +115,8 @@ public class Chunk : IDisposable
 
 
         RectI searchRegion = passedSearchRegion ?? new RectI(VecI.Zero, Surface.Size);
         RectI searchRegion = passedSearchRegion ?? new RectI(VecI.Zero, Surface.Size);
 
 
-        ulong* ptr = (ulong*)Surface.PixelBuffer;
+        using var pixmap = Surface.PeekPixels();
+        ulong* ptr = (ulong*)pixmap.GetPixels();
         for (int y = searchRegion.Top; y < searchRegion.Bottom; y++)
         for (int y = searchRegion.Top; y < searchRegion.Bottom; y++)
         {
         {
             for (int x = searchRegion.Left; x < searchRegion.Right; x++)
             for (int x = searchRegion.Left; x < searchRegion.Right; x++)
@@ -133,11 +140,15 @@ public class Chunk : IDisposable
     /// </summary>
     /// </summary>
     public void Dispose()
     public void Dispose()
     {
     {
-        if (returned)
+        returned = true;
+        internalSurface.Dispose();
+        // Leaving this in case chunk pooling turns out to be better
+        /*if (returned)
             return;
             return;
         Interlocked.Decrement(ref chunkCounter);
         Interlocked.Decrement(ref chunkCounter);
         Surface.DrawingSurface.Canvas.Clear();
         Surface.DrawingSurface.Canvas.Clear();
+        Surface.DrawingSurface.Canvas.SetMatrix(Matrix3X3.Identity);
         ChunkPool.Instance.Push(this);
         ChunkPool.Instance.Push(this);
-        returned = true;
+        returned = true;*/
     }
     }
 }
 }

+ 96 - 5
src/ChunkyImageLib/ChunkyImage.cs

@@ -1,4 +1,5 @@
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
+using System.Diagnostics;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using ChunkyImageLib.Operations;
@@ -6,6 +7,7 @@ using OneOf;
 using OneOf.Types;
 using OneOf.Types;
 using PixiEditor.Common;
 using PixiEditor.Common;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
@@ -268,7 +270,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                 var image = GetCommittedChunk(chunk, ChunkResolution.Full);
                 var image = GetCommittedChunk(chunk, ChunkResolution.Full);
                 if (image is null)
                 if (image is null)
                     continue;
                     continue;
-                output.EnqueueDrawImage(chunk * FullChunkSize, image.Surface);
+                output.EnqueueDrawTexture(chunk * FullChunkSize, image.Surface);
             }
             }
 
 
             output.CommitChanges();
             output.CommitChanges();
@@ -341,6 +343,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
 
 
             // something is queued, blend mode is not Src so we have to do merging
             // something is queued, blend mode is not Src so we have to do merging
             {
             {
+                using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
                 Chunk? committedChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
                 Chunk? committedChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
                 Chunk? latestChunk = GetLatestChunk(chunkPos, ChunkResolution.Full);
                 Chunk? latestChunk = GetLatestChunk(chunkPos, ChunkResolution.Full);
                 Color committedColor = committedChunk is null
                 Color committedColor = committedChunk is null
@@ -354,8 +357,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                 using Chunk tempChunk = Chunk.Create(ProcessingColorSpace, ChunkResolution.Eighth);
                 using Chunk tempChunk = Chunk.Create(ProcessingColorSpace, ChunkResolution.Eighth);
                 using Paint committedPaint = new Paint() { Color = committedColor, BlendMode = BlendMode.Src };
                 using Paint committedPaint = new Paint() { Color = committedColor, BlendMode = BlendMode.Src };
                 using Paint latestPaint = new Paint() { Color = latestColor, BlendMode = this.blendMode };
                 using Paint latestPaint = new Paint() { Color = latestColor, BlendMode = this.blendMode };
-                tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, committedPaint);
-                tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, latestPaint);
+                tempChunk.Surface.DrawingSurface.Canvas.DrawRect(new RectD(VecI.Zero, new VecD(1)), committedPaint);
+                tempChunk.Surface.DrawingSurface.Canvas.DrawRect(new RectD(VecI.Zero, new VecI(1)), latestPaint);
                 return tempChunk.Surface.GetSrgbPixel(VecI.Zero);
                 return tempChunk.Surface.GetSrgbPixel(VecI.Zero);
             }
             }
         }
         }
@@ -407,6 +410,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                 return false;
                 return false;
             }
             }
 
 
+            using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
             // combine with committed and then draw
             // combine with committed and then draw
             using var tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
             using var tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
             tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0,
             tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0,
@@ -422,6 +426,66 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         }
         }
     }
     }
 
 
+    public bool DrawCachedMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface,
+        VecD pos,
+        Paint? paint = null, SamplingOptions? sampling = null)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            OneOf<None, EmptyChunk, Chunk> latestChunk;
+            {
+                var chunk = MaybeGetLatestChunk(chunkPos, resolution);
+                if (latestChunksData[resolution].TryGetValue(chunkPos, out var chunkData) && chunkData.IsDeleted)
+                {
+                    latestChunk = new EmptyChunk();
+                }
+                else
+                {
+                    latestChunk = chunk is null ? new None() : chunk;
+                }
+            }
+
+            var committedChunk = GetCommittedChunk(chunkPos, resolution);
+
+            // draw committed directly
+            if (latestChunk.IsT0 || latestChunk.IsT1 && committedChunk is not null && blendMode != BlendMode.Src)
+            {
+                if (committedChunk is null)
+                    return false;
+                committedChunk.DrawChunkOn(surface, pos, paint, sampling);
+                return true;
+            }
+
+            // no need to combine with committed, draw directly
+            if (blendMode == BlendMode.Src || committedChunk is null)
+            {
+                if (latestChunk.IsT2)
+                {
+                    latestChunk.AsT2.DrawChunkOn(surface, pos, paint, sampling);
+                    return true;
+                }
+
+                return false;
+            }
+
+            using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
+
+            // combine with committed and then draw
+            using var tempChunk = Chunk.Create(ProcessingColorSpace, resolution);
+            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0,
+                ReplacingPaint);
+            blendModePaint.BlendMode = blendMode;
+            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.DrawingSurface, 0, 0,
+                blendModePaint);
+            if (lockTransparency)
+                OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
+            tempChunk.DrawChunkOn(surface, pos, paint, sampling);
+
+            return true;
+        }
+    }
+
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public bool LatestOrCommittedChunkExists(VecI chunkPos)
     public bool LatestOrCommittedChunkExists(VecI chunkPos)
     {
     {
@@ -637,12 +701,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     /// Surface is NOT THREAD SAFE, so if you pass a Surface here with copyImage == false you must not do anything with that surface anywhere (not even read) until CommitChanges/CancelChanges is called.
     /// Surface is NOT THREAD SAFE, so if you pass a Surface here with copyImage == false you must not do anything with that surface anywhere (not even read) until CommitChanges/CancelChanges is called.
     /// </summary>
     /// </summary>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawImage(Matrix3X3 transformMatrix, Surface image, Paint? paint = null, bool copyImage = true)
+    public void EnqueueDrawImage(Matrix3X3 transformMatrix, Surface image, SamplingOptions samplingOptions, Paint? paint = null, bool copyImage = true)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
             ThrowIfDisposed();
             ThrowIfDisposed();
-            ImageOperation operation = new(transformMatrix, image, paint, copyImage);
+            ImageOperation operation = new(transformMatrix, image, samplingOptions, paint, copyImage);
             EnqueueOperation(operation);
             EnqueueOperation(operation);
         }
         }
     }
     }
@@ -970,6 +1034,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
+            using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
             ThrowIfDisposed();
             ThrowIfDisposed();
             var affectedArea = FindAffectedArea();
             var affectedArea = FindAffectedArea();
 
 
@@ -1060,6 +1125,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                         continue;
                         continue;
                     }
                     }
 
 
+                    using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
+
                     //blend
                     //blend
                     blendModePaint.BlendMode = blendMode;
                     blendModePaint.BlendMode = blendMode;
                     if (lockTransparency)
                     if (lockTransparency)
@@ -1139,6 +1206,26 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         }
         }
     }
     }
 
 
+    public Dictionary<VecI, Surface> CloneAllCommitedNonEmptyChunks()
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            var dict = new Dictionary<VecI, Surface>();
+            foreach (var (pos, chunk) in committedChunks[ChunkResolution.Full])
+            {
+                if (chunk.FindPreciseBounds().HasValue)
+                {
+                    var surf = new Surface(chunk.Surface.ImageInfo);
+                    surf.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0);
+                    dict[pos] = surf;
+                }
+            }
+
+            return dict;
+        }
+    }
+
     /// <returns>
     /// <returns>
     /// Chunks affected by operations that haven't been committed yet
     /// Chunks affected by operations that haven't been committed yet
     /// </returns>
     /// </returns>
@@ -1274,6 +1361,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         if (operation is ClearOperation)
         if (operation is ClearOperation)
             return true;
             return true;
 
 
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
+
         if (operation is IDrawOperation chunkOperation)
         if (operation is IDrawOperation chunkOperation)
         {
         {
             if (combinedRasterClips.IsT1) // Nothing is visible
             if (combinedRasterClips.IsT1) // Nothing is visible
@@ -1399,6 +1488,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     /// </summary>
     /// </summary>
     private Chunk GetOrCreateCommittedChunk(VecI chunkPos, ChunkResolution resolution)
     private Chunk GetOrCreateCommittedChunk(VecI chunkPos, ChunkResolution resolution)
     {
     {
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
         // committed chunk of the same resolution exists
         // committed chunk of the same resolution exists
         Chunk? targetChunk = MaybeGetCommittedChunk(chunkPos, resolution);
         Chunk? targetChunk = MaybeGetCommittedChunk(chunkPos, resolution);
         if (targetChunk is not null)
         if (targetChunk is not null)
@@ -1441,6 +1531,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     /// </summary>
     /// </summary>
     private Chunk GetOrCreateLatestChunk(VecI chunkPos, ChunkResolution resolution)
     private Chunk GetOrCreateLatestChunk(VecI chunkPos, ChunkResolution resolution)
     {
     {
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
         // latest chunk exists
         // latest chunk exists
         Chunk? targetChunk = MaybeGetLatestChunk(chunkPos, resolution);
         Chunk? targetChunk = MaybeGetLatestChunk(chunkPos, resolution);
         if (targetChunk is not null)
         if (targetChunk is not null)

+ 88 - 9
src/ChunkyImageLib/ChunkyImageEx.cs

@@ -1,4 +1,5 @@
-using ChunkyImageLib.DataHolders;
+using System.Diagnostics;
+using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using ChunkyImageLib.Operations;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
@@ -6,6 +7,7 @@ using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
 namespace ChunkyImageLib;
 namespace ChunkyImageLib;
+
 public static class IReadOnlyChunkyImageEx
 public static class IReadOnlyChunkyImageEx
 {
 {
     /// <summary>
     /// <summary>
@@ -20,11 +22,29 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="paint">Paint to use for drawing</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawMostUpToDateRegionOn
     public static void DrawMostUpToDateRegionOn
     (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface,
     (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface,
-        VecD pos, Paint? paint = null, SamplingOptions? sampling = null)
+        VecD pos, Paint? paint = null, SamplingOptions? sampling = null, bool drawPaintOnEmpty = false)
+    {
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint, sampling, drawPaintOnEmpty);
+    }
+
+    /// <summary>
+    /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
+    /// The region is taken from the most up to date version of the <see cref="ChunkyImage"/>
+    /// </summary>
+    /// <param name="image"><see cref="ChunkyImage"/> to extract the region from</param>
+    /// <param name="fullResRegion">The region to extract</param>
+    /// <param name="resolution">Chunk resolution</param>
+    /// <param name="surface">Surface to draw onto</param>
+    /// <param name="pos">Starting position on the surface</param>
+    /// <param name="paint">Paint to use for drawing</param>
+    public static void DrawMostUpToDateRegionOnWithAffected
+    (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface,
+        AffectedArea affectedArea, VecD pos, Paint? paint = null, SamplingOptions? sampling = null, bool drawPaintOnEmpty = false)
     {
     {
-        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint, sampling);
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn,
+            image.DrawCachedMostUpToDateChunkOn, affectedArea, paint, sampling, drawPaintOnEmpty);
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
     /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
     /// The region is taken from the committed version of the <see cref="ChunkyImage"/>
     /// The region is taken from the committed version of the <see cref="ChunkyImage"/>
@@ -36,18 +56,56 @@ public static class IReadOnlyChunkyImageEx
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="pos">Starting position on the surface</param>
     /// <param name="paint">Paint to use for drawing</param>
     /// <param name="paint">Paint to use for drawing</param>
     public static void DrawCommittedRegionOn
     public static void DrawCommittedRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null, SamplingOptions? samplingOptions = null)
+    (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface,
+        VecI pos, Paint? paint = null, SamplingOptions? samplingOptions = null, bool drawPaintOnEmpty = false)
+    {
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint, samplingOptions, drawPaintOnEmpty);
+    }
+
+    private static void DrawRegionOn(
+        RectI fullResRegion,
+        ChunkResolution resolution,
+        DrawingSurface surface,
+        VecD pos,
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawingFunc,
+        Paint? paint = null, SamplingOptions? samplingOptions = null, bool drawPaintOnEmpty = false)
     {
     {
-        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint, samplingOptions);
+        int count = surface.Canvas.Save();
+        surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
+
+        VecI chunkTopLeft = OperationHelper.GetChunkPos(fullResRegion.TopLeft, ChunkyImage.FullChunkSize);
+        VecI chunkBotRight = OperationHelper.GetChunkPos(fullResRegion.BottomRight, ChunkyImage.FullChunkSize);
+        VecI offsetFullRes = (chunkTopLeft * ChunkyImage.FullChunkSize) - fullResRegion.Pos;
+        VecI offsetTargetRes = (VecI)(offsetFullRes * resolution.Multiplier());
+
+        for (int j = chunkTopLeft.Y; j <= chunkBotRight.Y; j++)
+        {
+            for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
+            {
+                var chunkPos = new VecI(i, j);
+                if (!drawingFunc(chunkPos, resolution, surface,
+                        offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint,
+                        samplingOptions) && paint != null && drawPaintOnEmpty)
+                {
+                    surface.Canvas.DrawRect(new RectD(
+                        offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos,
+                        new VecD(resolution.PixelSize())), paint);
+                }
+            }
+        }
+
+        surface.Canvas.RestoreToCount(count);
     }
     }
-    
+
     private static void DrawRegionOn(
     private static void DrawRegionOn(
         RectI fullResRegion,
         RectI fullResRegion,
         ChunkResolution resolution,
         ChunkResolution resolution,
         DrawingSurface surface,
         DrawingSurface surface,
         VecD pos,
         VecD pos,
         Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawingFunc,
         Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> drawingFunc,
-        Paint? paint = null, SamplingOptions? samplingOptions = null)
+        Func<VecI, ChunkResolution, DrawingSurface, VecD, Paint?, SamplingOptions?, bool> quickDrawingFunc,
+        AffectedArea area,
+        Paint? paint = null, SamplingOptions? samplingOptions = null, bool drawPaintOnEmpty = false)
     {
     {
         int count = surface.Canvas.Save();
         int count = surface.Canvas.Save();
         surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
         surface.Canvas.ClipRect(new RectD(pos, fullResRegion.Size));
@@ -62,7 +120,28 @@ public static class IReadOnlyChunkyImageEx
             for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
             for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
             {
             {
                 var chunkPos = new VecI(i, j);
                 var chunkPos = new VecI(i, j);
-                drawingFunc(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint, samplingOptions);
+                if (area.Chunks != null && area.Chunks.Contains(chunkPos))
+                {
+                    if (!drawingFunc(chunkPos, resolution, surface,
+                            offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint,
+                            samplingOptions) && paint != null && drawPaintOnEmpty)
+                    {
+                        surface.Canvas.DrawRect(new RectD(
+                            offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos,
+                            new VecD(resolution.PixelSize())), paint);
+                    }
+                }
+                else
+                {
+                    if (!quickDrawingFunc(chunkPos, resolution, surface,
+                            offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint,
+                            samplingOptions) && paint != null && drawPaintOnEmpty)
+                    {
+                        surface.Canvas.DrawRect(new RectD(
+                            offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos,
+                            new VecD(resolution.PixelSize())), paint);
+                    }
+                }
             }
             }
         }
         }
 
 

+ 1 - 1
src/ChunkyImageLib/CommittedChunkStorage.cs

@@ -36,7 +36,7 @@ public class CommittedChunkStorage : IDisposable
             if (chunk is null)
             if (chunk is null)
                 image.EnqueueClearRegion(new(pos * ChunkPool.FullChunkSize, new(ChunkPool.FullChunkSize, ChunkPool.FullChunkSize)));
                 image.EnqueueClearRegion(new(pos * ChunkPool.FullChunkSize, new(ChunkPool.FullChunkSize, ChunkPool.FullChunkSize)));
             else
             else
-                image.EnqueueDrawImage(pos * ChunkPool.FullChunkSize, chunk.Surface, ReplacingPaint);
+                image.EnqueueDrawTexture(pos * ChunkPool.FullChunkSize, chunk.Surface, ReplacingPaint);
         }
         }
     }
     }
 
 

+ 27 - 25
src/ChunkyImageLib/DataHolders/ColorBounds.cs

@@ -21,27 +21,9 @@ public struct ColorBounds
 
 
     public float UpperA { get; set; }
     public float UpperA { get; set; }
 
 
-    public ColorBounds(Color color, double tolerance = 0)
+    public ColorBounds(ColorF color, double tolerance = 0)
     {
     {
-        static (float lower, float upper) FindInclusiveBoundaryPremul(byte channel, float alpha)
-        {
-            float subHalf = channel > 0 ? channel - 1f : channel;
-            float addHalf = channel < 255 ? channel + 1f : channel;
-            
-            var lower = subHalf * alpha / 255f;
-            var upper = addHalf * alpha / 255f;
-            
-            return (lower, upper);
-        }
-
-        static (float lower, float upper) FindInclusiveBoundary(byte channel)
-        {
-            float subHalf = channel > 0 ? channel - .5f : channel;
-            float addHalf = channel < 255 ? channel + .5f : channel;
-            return (subHalf / 255f, addHalf / 255f);
-        }
-
-        float a = color.A / 255f;
+        float a = color.A;
 
 
         (LowerR, UpperR) = FindInclusiveBoundaryPremul(color.R, a);
         (LowerR, UpperR) = FindInclusiveBoundaryPremul(color.R, a);
         LowerR -= (float)tolerance;
         LowerR -= (float)tolerance;
@@ -60,6 +42,26 @@ public struct ColorBounds
         UpperA += (float)tolerance;
         UpperA += (float)tolerance;
     }
     }
 
 
+    private static (float lower, float upper) FindInclusiveBoundaryPremul(float channel, float alpha)
+    {
+        var step = 1f / 255f;
+        float subHalf = channel > 0 ? channel - step : channel;
+        float addHalf = channel < 1 ? channel + step : channel;
+
+        var lower = subHalf * alpha;
+        var upper = addHalf * alpha;
+
+        return (lower, upper);
+    }
+
+    private static (float lower, float upper) FindInclusiveBoundary(float channel)
+    {
+        float halfStep = 0.5f / 255f;
+        float subHalf = channel > 0 ? channel - halfStep : channel;
+        float addHalf = channel < 1 ? channel + halfStep : channel;
+        return (subHalf, addHalf);
+    }
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public unsafe bool IsWithinBounds(Half* pixel)
     public unsafe bool IsWithinBounds(Half* pixel)
     {
     {
@@ -78,12 +80,12 @@ public struct ColorBounds
         return true;
         return true;
     }
     }
 
 
-    public bool IsWithinBounds(Color toCompare)
+    public bool IsWithinBounds(ColorF toCompare)
     {
     {
-        float a = toCompare.A / 255f;
-        float r = (toCompare.R / 255f) * a;
-        float g = (toCompare.G / 255f) * a;
-        float b = (toCompare.B / 255f) * a;
+        float a = toCompare.A;
+        float r = (toCompare.R) * a;
+        float g = (toCompare.G) * a;
+        float b = (toCompare.B) * a;
         
         
         if (r < LowerR || r > UpperR)
         if (r < LowerR || r > UpperR)
             return false;
             return false;

+ 1 - 0
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -11,6 +11,7 @@ namespace ChunkyImageLib;
 public interface IReadOnlyChunkyImage
 public interface IReadOnlyChunkyImage
 {
 {
     bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
     bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
+    bool DrawCachedMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
     bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
     bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecD pos, Paint? paint = null, SamplingOptions? sampling = null);
     RectI? FindChunkAlignedMostUpToDateBounds();
     RectI? FindChunkAlignedMostUpToDateBounds();
     RectI? FindChunkAlignedCommittedBounds();
     RectI? FindChunkAlignedCommittedBounds();

+ 1 - 2
src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs

@@ -70,9 +70,8 @@ internal class DrawingSurfaceLineOperation : IMirroredDrawOperation
             newTo = (VecI)newTo.ReflectY((double)horAxisY).Round();
             newTo = (VecI)newTo.ReflectY((double)horAxisY).Round();
         }
         }
 
 
-        Color color = paint.Paintable is ColorPaintable colorPaintable ? colorPaintable.Color : paint.Color;
 
 
-        return new DrawingSurfaceLineOperation(newFrom, newTo, paint.StrokeCap, paint.StrokeWidth, color, paint.BlendMode);
+        return new DrawingSurfaceLineOperation(newFrom, newTo, paint);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 28 - 19
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -8,6 +8,7 @@ using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
 namespace ChunkyImageLib.Operations;
 namespace ChunkyImageLib.Operations;
+
 internal class EllipseOperation : IMirroredDrawOperation
 internal class EllipseOperation : IMirroredDrawOperation
 {
 {
     public bool IgnoreEmptyChunks => false;
     public bool IgnoreEmptyChunks => false;
@@ -21,14 +22,15 @@ internal class EllipseOperation : IMirroredDrawOperation
     private bool init = false;
     private bool init = false;
     private VectorPath? outerPath;
     private VectorPath? outerPath;
     private VectorPath? innerPath;
     private VectorPath? innerPath;
-    
+
     private VectorPath? ellipseOutline;
     private VectorPath? ellipseOutline;
     private VecF[]? ellipse;
     private VecF[]? ellipse;
     private VecF[]? ellipseFill;
     private VecF[]? ellipseFill;
     private RectI? ellipseFillRect;
     private RectI? ellipseFillRect;
     private bool antialiased;
     private bool antialiased;
 
 
-    public EllipseOperation(RectD location, Paintable strokePaintable, Paintable fillPaintable, float strokeWidth, double rotationRad,
+    public EllipseOperation(RectD location, Paintable strokePaintable, Paintable fillPaintable, float strokeWidth,
+        double rotationRad,
         bool antiAliased, Paint? paint = null)
         bool antiAliased, Paint? paint = null)
     {
     {
         this.location = location;
         this.location = location;
@@ -92,7 +94,7 @@ internal class EllipseOperation : IMirroredDrawOperation
 
 
         if (antialiased)
         if (antialiased)
         {
         {
-            DrawAntiAliased(surf);   
+            DrawAntiAliased(surf);
         }
         }
         else
         else
         {
         {
@@ -109,22 +111,26 @@ internal class EllipseOperation : IMirroredDrawOperation
         {
         {
             if (Math.Abs(rotation) < 0.001 && strokeWidth > 0)
             if (Math.Abs(rotation) < 0.001 && strokeWidth > 0)
             {
             {
+                RectD rect = (((RectD?)(ellipseFillRect)) ?? (RectD?)location).Value;
+                fillPaintable.Bounds = location;
                 if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 {
                 {
                     paint.SetPaintable(fillPaintable);
                     paint.SetPaintable(fillPaintable);
                     surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
                     surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
-                    surf.Canvas.DrawRect((RectD)ellipseFillRect!.Value, paint);
+                    surf.Canvas.DrawRect(rect, paint);
                 }
                 }
-                
+
                 paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
                 paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
                 paint.StrokeWidth = 1f;
                 paint.StrokeWidth = 1f;
                 surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
                 surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
+
+                fillPaintable.Bounds = null;
             }
             }
             else
             else
             {
             {
                 surf.Canvas.Save();
                 surf.Canvas.Save();
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
-                
+
                 if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 {
                 {
                     paint.SetPaintable(fillPaintable);
                     paint.SetPaintable(fillPaintable);
@@ -151,14 +157,15 @@ internal class EllipseOperation : IMirroredDrawOperation
                 surf.Canvas.Save();
                 surf.Canvas.Save();
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.ClipPath(innerPath!);
                 surf.Canvas.ClipPath(innerPath!);
-                surf.Canvas.DrawPaintable(fillPaintable, paint.BlendMode);
+                surf.Canvas.DrawPaintable(fillPaintable, paint.BlendMode, location);
                 surf.Canvas.Restore();
                 surf.Canvas.Restore();
             }
             }
+
             surf.Canvas.Save();
             surf.Canvas.Save();
             surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
             surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
             surf.Canvas.ClipPath(outerPath!);
             surf.Canvas.ClipPath(outerPath!);
             surf.Canvas.ClipPath(innerPath!, ClipOperation.Difference);
             surf.Canvas.ClipPath(innerPath!, ClipOperation.Difference);
-            surf.Canvas.DrawPaintable(strokePaintable, paint.BlendMode);
+            surf.Canvas.DrawPaintable(strokePaintable, paint.BlendMode, location);
             surf.Canvas.Restore();
             surf.Canvas.Restore();
         }
         }
     }
     }
@@ -167,24 +174,24 @@ internal class EllipseOperation : IMirroredDrawOperation
     {
     {
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
         surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
-        
+
         paint.IsAntiAliased = false;
         paint.IsAntiAliased = false;
         paint.SetPaintable(fillPaintable);
         paint.SetPaintable(fillPaintable);
         paint.Style = PaintStyle.Fill;
         paint.Style = PaintStyle.Fill;
-        
+
         RectD fillRect = ((RectD)location).Inflate(-strokeWidth / 2f);
         RectD fillRect = ((RectD)location).Inflate(-strokeWidth / 2f);
-        
+
         surf.Canvas.DrawOval(fillRect.Center, fillRect.Size / 2f, paint);
         surf.Canvas.DrawOval(fillRect.Center, fillRect.Size / 2f, paint);
 
 
         paint.IsAntiAliased = true;
         paint.IsAntiAliased = true;
         paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
         paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
         paint.Style = PaintStyle.Stroke;
         paint.Style = PaintStyle.Stroke;
         paint.StrokeWidth = strokeWidth <= 0 ? 1f : strokeWidth;
         paint.StrokeWidth = strokeWidth <= 0 ? 1f : strokeWidth;
-        
+
         RectD strokeRect = ((RectD)location).Inflate((-strokeWidth / 2f));
         RectD strokeRect = ((RectD)location).Inflate((-strokeWidth / 2f));
-        
+
         surf.Canvas.DrawOval(strokeRect.Center, strokeRect.Size / 2f, paint);
         surf.Canvas.DrawOval(strokeRect.Center, strokeRect.Size / 2f, paint);
-        
+
         surf.Canvas.Restore();
         surf.Canvas.Restore();
     }
     }
 
 
@@ -193,14 +200,15 @@ internal class EllipseOperation : IMirroredDrawOperation
         ShapeCorners corners = new((RectD)location);
         ShapeCorners corners = new((RectD)location);
         corners = corners.AsRotated(rotation, (VecD)location.Center);
         corners = corners.AsRotated(rotation, (VecD)location.Center);
         RectI bounds = (RectI)corners.AABBBounds.RoundOutwards();
         RectI bounds = (RectI)corners.AABBBounds.RoundOutwards();
-        
+
         var chunks = OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
         var chunks = OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
         if (!fillPaintable?.AnythingVisible ?? false)
         if (!fillPaintable?.AnythingVisible ?? false)
         {
         {
-             chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
-                (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2, ChunkyImage.FullChunkSize, rotation));
+            chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
+            (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2,
+                ChunkyImage.FullChunkSize, rotation));
         }
         }
-        
+
         return new AffectedArea(chunks, bounds);
         return new AffectedArea(chunks, bounds);
     }
     }
 
 
@@ -226,7 +234,8 @@ internal class EllipseOperation : IMirroredDrawOperation
             ((IPositionPaintable)finalStrokePaintable).Position = newLocation.Center;
             ((IPositionPaintable)finalStrokePaintable).Position = newLocation.Center;
         }
         }
 
 
-        return new EllipseOperation(newLocation, finalStrokePaintable, finalFillPaintable, strokeWidth, rotation, antialiased, paint);
+        return new EllipseOperation(newLocation, finalStrokePaintable, finalFillPaintable, strokeWidth, rotation,
+            antialiased, paint);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 7 - 3
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -1,6 +1,7 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
@@ -12,6 +13,7 @@ internal class ImageOperation : IMirroredDrawOperation
     private ShapeCorners corners;
     private ShapeCorners corners;
     private Surface toPaint;
     private Surface toPaint;
     private bool imageWasCopied = false;
     private bool imageWasCopied = false;
+    private SamplingOptions samplingOptions = SamplingOptions.Default;
     private readonly Paint? customPaint;
     private readonly Paint? customPaint;
 
 
     public bool IgnoreEmptyChunks => false;
     public bool IgnoreEmptyChunks => false;
@@ -31,6 +33,7 @@ internal class ImageOperation : IMirroredDrawOperation
         transformMatrix = Matrix3X3.CreateIdentity();
         transformMatrix = Matrix3X3.CreateIdentity();
         transformMatrix.TransX = pos.X;
         transformMatrix.TransX = pos.X;
         transformMatrix.TransY = pos.Y;
         transformMatrix.TransY = pos.Y;
+        this.samplingOptions = samplingOptions;
 
 
         // copying is needed for thread safety
         // copying is needed for thread safety
         if (copyImage)
         if (copyImage)
@@ -56,7 +59,7 @@ internal class ImageOperation : IMirroredDrawOperation
         imageWasCopied = copyImage;
         imageWasCopied = copyImage;
     }
     }
 
 
-    public ImageOperation(Matrix3X3 transformMatrix, Surface image, Paint? paint = null, bool copyImage = true)
+    public ImageOperation(Matrix3X3 transformMatrix, Surface image, SamplingOptions samplingOptions, Paint? paint = null, bool copyImage = true)
     {
     {
         if (paint is not null)
         if (paint is not null)
             customPaint = paint.Clone();
             customPaint = paint.Clone();
@@ -69,6 +72,7 @@ internal class ImageOperation : IMirroredDrawOperation
             BottomRight = transformMatrix.MapPoint(image.Size),
             BottomRight = transformMatrix.MapPoint(image.Size),
         };
         };
         this.transformMatrix = transformMatrix;
         this.transformMatrix = transformMatrix;
+        this.samplingOptions = samplingOptions;
 
 
         // copying is needed for thread safety
         // copying is needed for thread safety
         if (copyImage)
         if (copyImage)
@@ -100,12 +104,12 @@ internal class ImageOperation : IMirroredDrawOperation
             ShapeCorners chunkCorners = new ShapeCorners(new RectD(VecD.Zero, targetChunk.PixelSize));
             ShapeCorners chunkCorners = new ShapeCorners(new RectD(VecD.Zero, targetChunk.PixelSize));
             RectD rect = chunkCorners.WithMatrix(finalMatrix.Invert()).AABBBounds;
             RectD rect = chunkCorners.WithMatrix(finalMatrix.Invert()).AABBBounds;
 
 
-            targetChunk.Surface.DrawingSurface.Canvas.DrawImage(snapshot, rect, rect, customPaint);
+            targetChunk.Surface.DrawingSurface.Canvas.DrawImage(snapshot, rect, rect, customPaint, samplingOptions);
         }
         }
         else
         else
         {
         {
             // Slower, but works with perspective transformation
             // Slower, but works with perspective transformation
-            targetChunk.Surface.DrawingSurface.Canvas.DrawImage(snapshot, 0, 0, customPaint);
+            targetChunk.Surface.DrawingSurface.Canvas.DrawImage(snapshot, 0, 0, samplingOptions, customPaint);
         }
         }
 
 
         targetChunk.Surface.DrawingSurface.Canvas.Restore();
         targetChunk.Surface.DrawingSurface.Canvas.Restore();

+ 86 - 24
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -1,7 +1,9 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Utils;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
 namespace ChunkyImageLib.Operations;
 namespace ChunkyImageLib.Operations;
@@ -40,7 +42,7 @@ internal class RectangleOperation : IMirroredDrawOperation
         surf.Canvas.RotateRadians((float)Data.Angle, (float)rect.Center.X, (float)rect.Center.Y);
         surf.Canvas.RotateRadians((float)Data.Angle, (float)rect.Center.X, (float)rect.Center.Y);
 
 
         double maxRadiusInPx = Math.Min(Data.Size.X, Data.Size.Y) / 2;
         double maxRadiusInPx = Math.Min(Data.Size.X, Data.Size.Y) / 2;
-        double radiusInPx = Data.CornerRadius * maxRadiusInPx;
+        double radiusInPx = Data.CornerRadius * Math.Abs(maxRadiusInPx);
 
 
         if (Data.AntiAliasing)
         if (Data.AntiAliasing)
         {
         {
@@ -56,6 +58,7 @@ internal class RectangleOperation : IMirroredDrawOperation
 
 
     private void DrawPixelPerfect(DrawingSurface surf, RectD rect, RectD innerRect, double radius)
     private void DrawPixelPerfect(DrawingSurface surf, RectD rect, RectD innerRect, double radius)
     {
     {
+        VecD vecInnerRadius = new VecD(Math.Max(0, radius - Data.StrokeWidth));
         // draw fill
         // draw fill
         if (Data.FillPaintable.AnythingVisible)
         if (Data.FillPaintable.AnythingVisible)
         {
         {
@@ -66,10 +69,10 @@ internal class RectangleOperation : IMirroredDrawOperation
             }
             }
             else
             else
             {
             {
-                surf.Canvas.ClipRoundRect(innerRect, new VecD(radius), ClipOperation.Intersect);
+                surf.Canvas.ClipRoundRect(innerRect, vecInnerRadius, ClipOperation.Intersect);
             }
             }
 
 
-            surf.Canvas.DrawPaintable(Data.FillPaintable, Data.BlendMode);
+            surf.Canvas.DrawPaintable(Data.FillPaintable, Data.BlendMode, rect);
             surf.Canvas.RestoreToCount(saved);
             surf.Canvas.RestoreToCount(saved);
         }
         }
 
 
@@ -84,15 +87,26 @@ internal class RectangleOperation : IMirroredDrawOperation
         {
         {
             VecD vecRadius = new VecD(radius);
             VecD vecRadius = new VecD(radius);
             surf.Canvas.ClipRoundRect(rect, vecRadius, ClipOperation.Intersect);
             surf.Canvas.ClipRoundRect(rect, vecRadius, ClipOperation.Intersect);
-            surf.Canvas.ClipRoundRect(innerRect, vecRadius, ClipOperation.Difference);
+            surf.Canvas.ClipRoundRect(innerRect, vecInnerRadius, ClipOperation.Difference);
         }
         }
 
 
-        surf.Canvas.DrawPaintable(Data.Stroke, Data.BlendMode);
+        surf.Canvas.DrawPaintable(Data.Stroke, Data.BlendMode, rect);
     }
     }
 
 
     private void DrawAntiAliased(DrawingSurface surf, RectD rect, double radius)
     private void DrawAntiAliased(DrawingSurface surf, RectD rect, double radius)
     {
     {
-        // draw fill
+        // shrink radius too so corners match inner curve
+        // Draw fill first
+        if (Data.FillPaintable != null)
+        {
+            Data.FillPaintable.Bounds = rect;
+        }
+
+        if (Data.Stroke != null)
+        {
+            Data.Stroke.Bounds = rect;
+        }
+
         if (Data.FillPaintable.AnythingVisible)
         if (Data.FillPaintable.AnythingVisible)
         {
         {
             int saved = surf.Canvas.Save();
             int saved = surf.Canvas.Save();
@@ -100,35 +114,79 @@ internal class RectangleOperation : IMirroredDrawOperation
             paint.StrokeWidth = 0;
             paint.StrokeWidth = 0;
             paint.SetPaintable(Data.FillPaintable);
             paint.SetPaintable(Data.FillPaintable);
             paint.Style = PaintStyle.Fill;
             paint.Style = PaintStyle.Fill;
+            RectD fillRect = rect;
+            double innerRadius = Math.Max(0, radius - Data.StrokeWidth);
+            bool hasStroke = Data is { StrokeWidth: > 0, Stroke.AnythingVisible: true };
+            if (hasStroke)
+            {
+                paint.IsAntiAliased = false;
+                fillRect = rect.Inflate(-Data.StrokeWidth + 0.5);
+                surf.Canvas.ClipRoundRect(fillRect, new VecD(innerRadius), ClipOperation.Intersect);
+            }
+
             if (radius == 0)
             if (radius == 0)
             {
             {
-                surf.Canvas.DrawRect((float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height, paint);
+                surf.Canvas.DrawRect((float)fillRect.Left, (float)fillRect.Top,
+                    (float)fillRect.Width, (float)fillRect.Height, paint);
             }
             }
             else
             else
             {
             {
-                surf.Canvas.DrawRoundRect((float)rect.Left, (float)rect.Top, (float)rect.Width,
-                    (float)rect.Height, (float)radius, (float)radius, paint);
+                if (hasStroke)
+                {
+                    surf.Canvas.DrawPaintable(Data.FillPaintable, Data.BlendMode);
+                }
+                else
+                {
+                    surf.Canvas.DrawRoundRect((float)fillRect.Left, (float)fillRect.Top,
+                        (float)fillRect.Width, (float)fillRect.Height,
+                        (float)innerRadius, (float)innerRadius, paint);
+                }
             }
             }
 
 
             surf.Canvas.RestoreToCount(saved);
             surf.Canvas.RestoreToCount(saved);
         }
         }
 
 
-        // draw stroke
-        surf.Canvas.Save();
-        paint.StrokeWidth = Data.StrokeWidth > 0 ? Data.StrokeWidth : 1;
-        paint.SetPaintable(Data.StrokeWidth > 0 ? Data.Stroke : Data.FillPaintable);
-        paint.Style = PaintStyle.Stroke;
-        RectD innerRect = rect.Inflate(-Data.StrokeWidth / 2f);
+        bool hasFill = Data.FillPaintable.AnythingVisible;
 
 
-        if (radius == 0)
-        {
-            surf.Canvas.DrawRect((float)innerRect.Left, (float)innerRect.Top, (float)innerRect.Width,
-                (float)innerRect.Height, paint);
-        }
-        else
+        // Draw stroke fully inside
+        if (Data.StrokeWidth > 0)
         {
         {
-            surf.Canvas.DrawRoundRect((float)innerRect.Left, (float)innerRect.Top, (float)innerRect.Width,
-                (float)innerRect.Height, (float)radius, (float)radius, paint);
+            surf.Canvas.Save();
+
+            paint.StrokeWidth = Data.StrokeWidth;
+            paint.SetPaintable(Data.Stroke);
+            paint.Style = PaintStyle.Stroke;
+            paint.IsAntiAliased = Data.AntiAliasing;
+
+            // shrink rect so stroke is fully inside
+            RectD innerRect = rect.Inflate(-Data.StrokeWidth / 2f);
+
+            double innerRadius = Math.Max(0, radius - Data.StrokeWidth / 2f);
+
+            if (radius > 0 && innerRadius <= 0)
+            {
+                innerRadius = 0.0001;
+            }
+
+            if (innerRadius == 0)
+            {
+                surf.Canvas.DrawRect((float)innerRect.Left, (float)innerRect.Top,
+                    (float)innerRect.Width, (float)innerRect.Height, paint);
+            }
+            else
+            {
+                surf.Canvas.DrawRoundRect((float)innerRect.Left, (float)innerRect.Top,
+                    (float)innerRect.Width, (float)innerRect.Height,
+                    (float)innerRadius, (float)innerRadius, paint);
+            }
+
+            if(Data.FillPaintable != null)
+                Data.FillPaintable.Bounds = null;
+
+            if(Data.Stroke != null)
+                Data.Stroke.Bounds = null;
+
+            surf.Canvas.Restore();
         }
         }
     }
     }
 
 
@@ -149,10 +207,14 @@ internal class RectangleOperation : IMirroredDrawOperation
         var chunks =
         var chunks =
             OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle,
             OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle,
                 ChunkPool.FullChunkSize);
                 ChunkPool.FullChunkSize);
+
+        VecD radiusShrink = new VecD(Data.CornerRadius * Math.Min(Data.Size.X, Data.Size.Y),
+            Data.CornerRadius * Math.Min(Data.Size.X, Data.Size.Y));
+        VecD innerSize = Data.Size.Abs() - radiusShrink;
         chunks.ExceptWith(
         chunks.ExceptWith(
             OperationHelper.FindChunksFullyInsideRectangle(
             OperationHelper.FindChunksFullyInsideRectangle(
                 Data.Center,
                 Data.Center,
-                Data.Size.Abs() - new VecD(Data.StrokeWidth * 2, Data.StrokeWidth * 2),
+                innerSize - new VecD(Data.StrokeWidth * 2, Data.StrokeWidth * 2),
                 Data.Angle,
                 Data.Angle,
                 ChunkPool.FullChunkSize));
                 ChunkPool.FullChunkSize));
         return new(chunks, affRect);
         return new(chunks, affRect);

+ 1 - 1
src/ColorPicker

@@ -1 +1 @@
-Subproject commit 214215203d18b4088730311c2023aeddae731516
+Subproject commit 61055feed27354e6be969055fc0ee5db3c7d3b94

+ 0 - 81
src/Custom.ruleset

@@ -13,85 +13,4 @@
     <Rule Id="CA1303" Action="None" />
     <Rule Id="CA1303" Action="None" />
     <Rule Id="CA1416" Action="None" />
     <Rule Id="CA1416" Action="None" />
   </Rules>
   </Rules>
-  <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
-    <Rule Id="SA0001" Action="None" />
-    <Rule Id="SA1000" Action="None" />
-    <Rule Id="SA1005" Action="None" />
-    <Rule Id="SA1008" Action="None" />
-    <Rule Id="SA1009" Action="None" />
-    <Rule Id="SA1011" Action="None" />
-    <Rule Id="SA1023" Action="None" />
-    <Rule Id="SA1028" Action="None" />
-    <Rule Id="SA1101" Action="None" />
-    <Rule Id="SA1110" Action="None" />
-    <Rule Id="SA1111" Action="None" />
-    <Rule Id="SA1112" Action="None" />
-    <Rule Id="SA1117" Action="None" />
-    <Rule Id="SA1119" Action="None" />
-    <Rule Id="SA1121" Action="None" />
-    <Rule Id="SA1122" Action="None" />
-    <Rule Id="SA1124" Action="None" />
-    <Rule Id="SA1127" Action="None" />
-    <Rule Id="SA1128" Action="None" />
-    <Rule Id="SA1129" Action="None" />
-    <Rule Id="SA1130" Action="None" />
-    <Rule Id="SA1132" Action="None" />
-    <Rule Id="SA1135" Action="None" />
-    <Rule Id="SA1136" Action="None" />
-    <Rule Id="SA1139" Action="None" />
-    <Rule Id="SA1200" Action="None" />
-    <Rule Id="SA1201" Action="None" />
-    <Rule Id="SA1202" Action="None" />
-    <Rule Id="SA1204" Action="None" />
-    <Rule Id="SA1207" Action="None" />
-    <Rule Id="SA1208" Action="None" />
-    <Rule Id="SA1209" Action="None" />
-    <Rule Id="SA1210" Action="None" />
-    <Rule Id="SA1211" Action="None" />
-    <Rule Id="SA1214" Action="None" />
-    <Rule Id="SA1216" Action="None" />
-    <Rule Id="SA1217" Action="None" />
-    <Rule Id="SA1303" Action="None" />
-    <Rule Id="SA1304" Action="None" />
-    <Rule Id="SA1307" Action="None" />
-    <Rule Id="SA1309" Action="None" />
-    <Rule Id="SA1310" Action="None" />
-    <Rule Id="SA1311" Action="None" />
-    <Rule Id="SA1313" Action="None" />
-    <Rule Id="SA1316" Action="None" />
-    <Rule Id="SA1400" Action="None" />
-    <Rule Id="SA1401" Action="None" />
-    <Rule Id="SA1402" Action="None" />
-    <Rule Id="SA1405" Action="None" />
-    <Rule Id="SA1406" Action="None" />
-    <Rule Id="SA1407" Action="None" />
-    <Rule Id="SA1408" Action="None" />
-    <Rule Id="SA1410" Action="None" />
-    <Rule Id="SA1411" Action="None" />
-    <Rule Id="SA1413" Action="None" />
-    <Rule Id="SA1501" Action="None" />
-    <Rule Id="SA1502" Action="None" />
-    <Rule Id="SA1503" Action="None" />
-    <Rule Id="SA1505" Action="None" />
-    <Rule Id="SA1507" Action="None" />
-    <Rule Id="SA1508" Action="None" />
-    <Rule Id="SA1512" Action="None" />
-    <Rule Id="SA1513" Action="None" />
-    <Rule Id="SA1515" Action="None" />
-    <Rule Id="SA1516" Action="None" />
-    <Rule Id="SA1518" Action="None" />
-    <Rule Id="SA1600" Action="None" />
-    <Rule Id="SA1601" Action="None" />
-    <Rule Id="SA1602" Action="None" />
-    <Rule Id="SA1604" Action="None" />
-    <Rule Id="SA1605" Action="None" />
-    <Rule Id="SA1606" Action="None" />
-    <Rule Id="SA1607" Action="None" />
-    <Rule Id="SA1623" Action="None" />
-    <Rule Id="SA1629" Action="None" />
-    <Rule Id="SA1633" Action="None" />
-    <Rule Id="SA1642" Action="None" />
-    <Rule Id="SA1643" Action="None" />
-    <Rule Id="SA1648" Action="None" />
-  </Rules>
 </RuleSet>
 </RuleSet>

+ 3 - 9
src/Directory.Build.props

@@ -1,15 +1,9 @@
 <Project>
 <Project>
     <PropertyGroup>
     <PropertyGroup>
-        <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
-		    <AvaloniaVersion>11.3.0</AvaloniaVersion>
+        <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Custom.ruleset</CodeAnalysisRuleSet>
+		    <AvaloniaVersion>11.3.6</AvaloniaVersion>
     </PropertyGroup>
     </PropertyGroup>
-    <ItemGroup>
-        <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
-    </ItemGroup>
-    <ItemGroup>
-        <AdditionalFiles Include="$(SolutionDir)/stylecop.json" />
-    </ItemGroup>
-
+  
   <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows')) AND '$(Platform)' == 'x64'">
   <PropertyGroup Condition="$([MSBuild]::IsOsPlatform('Windows')) AND '$(Platform)' == 'x64'">
     <RuntimeIdentifier>win-x64</RuntimeIdentifier>
     <RuntimeIdentifier>win-x64</RuntimeIdentifier>
   </PropertyGroup>
   </PropertyGroup>

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit c53c9e66b01ea6ecc994b02f1fb1b8580eded004
+Subproject commit 932bde2cb38f5eb6b07b290fdd9c6522dc96dd74

+ 1 - 1
src/PixiDocks

@@ -1 +1 @@
-Subproject commit 6e745d0309ad7a00a53f62f2aa362be77903a5fd
+Subproject commit 1604a0bb1fdf1d0016bfc82752c85b3266bed2c2

+ 8 - 14
src/PixiEditor.AnimationRenderer.FFmpeg/FFMpegRenderer.cs

@@ -33,7 +33,10 @@ public class FFMpegRenderer : IAnimationRenderer
             MakeExecutableIfNeeded(binaryPath);
             MakeExecutableIfNeeded(binaryPath);
         }
         }
 
 
-        string paletteTempPath = Path.Combine(Path.GetDirectoryName(outputPath), "RenderTemp", "palette.png");
+        string tempPath = Path.Combine(Path.GetTempPath(), "PixiEditor", "Rendering");
+        Directory.CreateDirectory(tempPath);
+
+        string paletteTempPath = Path.Combine(tempPath, "palette.png");
 
 
         try
         try
         {
         {
@@ -46,12 +49,6 @@ public class FFMpegRenderer : IAnimationRenderer
 
 
             RawVideoPipeSource streamPipeSource = new(frames) { FrameRate = FrameRate, };
             RawVideoPipeSource streamPipeSource = new(frames) { FrameRate = FrameRate, };
 
 
-
-            if (!Directory.Exists(Path.GetDirectoryName(paletteTempPath)))
-            {
-                Directory.CreateDirectory(Path.GetDirectoryName(paletteTempPath));
-            }
-
             if (RequiresPaletteGeneration())
             if (RequiresPaletteGeneration())
             {
             {
                 GeneratePalette(streamPipeSource, paletteTempPath);
                 GeneratePalette(streamPipeSource, paletteTempPath);
@@ -98,7 +95,10 @@ public class FFMpegRenderer : IAnimationRenderer
             MakeExecutableIfNeeded(binaryPath);
             MakeExecutableIfNeeded(binaryPath);
         }
         }
 
 
-        string paletteTempPath = Path.Combine(Path.GetDirectoryName(outputPath), "RenderTemp", "palette.png");
+        string tempPath = Path.Combine(Path.GetTempPath(), "PixiEditor", "Rendering");
+        Directory.CreateDirectory(tempPath);
+
+        string paletteTempPath = Path.Combine(tempPath, "palette.png");
 
 
         try
         try
         {
         {
@@ -111,12 +111,6 @@ public class FFMpegRenderer : IAnimationRenderer
 
 
             RawVideoPipeSource streamPipeSource = new(frames) { FrameRate = FrameRate, };
             RawVideoPipeSource streamPipeSource = new(frames) { FrameRate = FrameRate, };
 
 
-
-            if (!Directory.Exists(Path.GetDirectoryName(paletteTempPath)))
-            {
-                Directory.CreateDirectory(Path.GetDirectoryName(paletteTempPath));
-            }
-
             if (RequiresPaletteGeneration())
             if (RequiresPaletteGeneration())
             {
             {
                 GeneratePalette(streamPipeSource, paletteTempPath);
                 GeneratePalette(streamPipeSource, paletteTempPath);

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameData.cs

@@ -8,7 +8,7 @@ public class KeyFrameData : IDisposable, IReadOnlyKeyFrameData
 {
 {
     public int StartFrame { get; set; }
     public int StartFrame { get; set; }
     public int Duration { get; set; }
     public int Duration { get; set; }
-    public Guid KeyFrameGuid { get; }
+    public Guid KeyFrameGuid { get; internal set; }
     public string AffectedElement { get; set; }
     public string AffectedElement { get; set; }
     public object Data { get; set; }
     public object Data { get; set; }
     public bool IsVisible { get; set; } = true;
     public bool IsVisible { get; set; } = true;

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -449,12 +449,12 @@ internal class Document : IChangeable, IReadOnlyDocument
 
 
     private void ExtractLayers(FolderNode folder, List<Guid> list)
     private void ExtractLayers(FolderNode folder, List<Guid> list)
     {
     {
-        List<Guid> result = new();
-        folder.TraverseBackwards(node =>
+        if(folder.Content.Connection == null) return;
+        folder.Content.Connection.Node.TraverseBackwards(node =>
         {
         {
-            if (node is LayerNode layer && !result.Contains(layer.Id))
+            if (node is LayerNode layer && !list.Contains(layer.Id))
             {
             {
-                result.Add(layer.Id);
+                list.Add(layer.Id);
             }
             }
 
 
             return true;
             return true;

+ 10 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Filter.cs

@@ -1,9 +1,10 @@
 using System.Diagnostics.Contracts;
 using System.Diagnostics.Contracts;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
+using PixiEditor.Common;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
 
-public sealed class Filter : IDisposable
+public sealed class Filter : IDisposable, ICacheable
 {
 {
     public Filter(ColorFilter? colorFilter, ImageFilter? imageFilter)
     public Filter(ColorFilter? colorFilter, ImageFilter? imageFilter)
     {
     {
@@ -45,4 +46,12 @@ public sealed class Filter : IDisposable
         ColorFilter?.Dispose();
         ColorFilter?.Dispose();
         ImageFilter?.Dispose();
         ImageFilter?.Dispose();
     }
     }
+
+    public int GetCacheHash()
+    {
+        HashCode hash = new();
+        hash.Add(ColorFilter?.GetHashCode() ?? 0);
+        hash.Add(ImageFilter?.GetHashCode() ?? 0);
+        return hash.ToHashCode();
+    }
 }
 }

+ 0 - 13
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IPreviewRenderable.cs

@@ -1,13 +0,0 @@
-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, RenderContext context,
-        string elementToRenderName);
-}

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

@@ -1,5 +1,5 @@
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 
-public interface IReadOnlyLayerNode : IReadOnlyStructureNode, IPreviewRenderable
+public interface IReadOnlyLayerNode : IReadOnlyStructureNode
 {
 {
 }
 }

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

@@ -4,7 +4,7 @@ using PixiEditor.Common;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 
-public interface IReadOnlyNodeGraph : ICacheable
+public interface IReadOnlyNodeGraph : ICacheable, IDisposable
 {
 {
     public IReadOnlyCollection<IReadOnlyNode> AllNodes { get; }
     public IReadOnlyCollection<IReadOnlyNode> AllNodes { get; }
     public IReadOnlyNode OutputNode { get; }
     public IReadOnlyNode OutputNode { get; }
@@ -15,4 +15,5 @@ public interface IReadOnlyNodeGraph : ICacheable
     public void Execute(RenderContext context);
     public void Execute(RenderContext context);
     public void Execute(IReadOnlyNode end, RenderContext context);
     public void Execute(IReadOnlyNode end, RenderContext context);
     Queue<IReadOnlyNode> CalculateExecutionQueue(IReadOnlyNode endNode);
     Queue<IReadOnlyNode> CalculateExecutionQueue(IReadOnlyNode endNode);
+    public IReadOnlyNodeGraph Clone();
 }
 }

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

@@ -8,7 +8,7 @@ using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
 
-public interface IReadOnlyStructureNode : IReadOnlyNode, ISceneObject, IChunkRenderable
+public interface IReadOnlyStructureNode : IReadOnlyNode, ISceneObject
 {
 {
     public InputProperty<float> Opacity { get; }
     public InputProperty<float> Opacity { get; }
     public InputProperty<bool> IsVisible { get; }
     public InputProperty<bool> IsVisible { get; }

+ 12 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs

@@ -19,4 +19,16 @@ public class SceneObjectRenderContext : RenderContext
         LocalBounds = localBounds;
         LocalBounds = localBounds;
         RenderSurfaceIsScene = renderSurfaceIsScene;
         RenderSurfaceIsScene = renderSurfaceIsScene;
     }
     }
+
+    public override RenderContext Clone()
+    {
+        return new SceneObjectRenderContext(TargetPropertyOutput, RenderSurface, LocalBounds, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, RenderSurfaceIsScene, ProcessingColorSpace, DesiredSamplingOptions, Opacity)
+        {
+            VisibleDocumentRegion = VisibleDocumentRegion,
+            AffectedArea = AffectedArea,
+            FullRerender = FullRerender,
+            TargetOutput = TargetOutput,
+            PreviewTextures = PreviewTextures,
+        };
+    }
 }
 }

+ 71 - 20
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -1,11 +1,12 @@
 using System.Collections.Immutable;
 using System.Collections.Immutable;
+using System.Diagnostics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
 
-public class NodeGraph : IReadOnlyNodeGraph, IDisposable
+public class NodeGraph : IReadOnlyNodeGraph
 {
 {
     private Dictionary<IReadOnlyNode, ImmutableList<IReadOnlyNode>?> cachedExecutionList;
     private Dictionary<IReadOnlyNode, ImmutableList<IReadOnlyNode>?> cachedExecutionList;
 
 
@@ -62,6 +63,52 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
         return new Queue<IReadOnlyNode>(CalculateExecutionQueueInternal(outputNode));
         return new Queue<IReadOnlyNode>(CalculateExecutionQueueInternal(outputNode));
     }
     }
 
 
+    public IReadOnlyNodeGraph Clone()
+    {
+        var newGraph = new NodeGraph();
+        var nodeMapping = new Dictionary<Node, Node>();
+
+        // Clone nodes
+        foreach (var node in Nodes)
+        {
+            var clonedNode = node.Clone(true);
+            newGraph.AddNode(clonedNode);
+            nodeMapping[node] = clonedNode;
+        }
+
+        // Re-establish connections
+        foreach (var node in Nodes)
+        {
+            var clonedNode = nodeMapping[node];
+            foreach (var input in node.InputProperties)
+            {
+                if (input.Connection != null)
+                {
+                    var connectedNode = input.Connection.Node;
+                    if (nodeMapping.TryGetValue(connectedNode as Node, out var clonedConnectedNode))
+                    {
+                        var clonedOutput = clonedConnectedNode.OutputProperties.FirstOrDefault(o =>
+                            o.InternalPropertyName == input.Connection.InternalPropertyName);
+                        var clonedInput = clonedNode.InputProperties.FirstOrDefault(i =>
+                            i.InternalPropertyName == input.InternalPropertyName);
+                        if (clonedOutput != null && clonedInput != null)
+                        {
+                            clonedOutput.ConnectTo(clonedInput);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Set custom output node if applicable
+        if (CustomOutputNode != null && nodeMapping.TryGetValue(CustomOutputNode, out var mappedOutputNode))
+        {
+            newGraph.CustomOutputNode = mappedOutputNode;
+        }
+
+        return newGraph;
+    }
+
     private ImmutableList<IReadOnlyNode> CalculateExecutionQueueInternal(IReadOnlyNode outputNode)
     private ImmutableList<IReadOnlyNode> CalculateExecutionQueueInternal(IReadOnlyNode outputNode)
     {
     {
         var cached = this.cachedExecutionList?.GetValueOrDefault(outputNode);
         var cached = this.cachedExecutionList?.GetValueOrDefault(outputNode);
@@ -91,12 +138,8 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
     public bool TryTraverse(Action<IReadOnlyNode> action)
     public bool TryTraverse(Action<IReadOnlyNode> action)
     {
     {
         if (OutputNode == null) return false;
         if (OutputNode == null) return false;
-        return TryTraverse(OutputNode, action);
-    }
 
 
-    public bool TryTraverse(IReadOnlyNode end, Action<IReadOnlyNode> action)
-    {
-        var queue = CalculateExecutionQueueInternal(end);
+        var queue = CalculateExecutionQueueInternal(OutputNode);
 
 
         foreach (var node in queue)
         foreach (var node in queue)
         {
         {
@@ -106,29 +149,36 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
         return true;
         return true;
     }
     }
 
 
-    public void Execute(IReadOnlyNode end, RenderContext context)
+    bool isexecuting = false;
+
+    public void Execute(RenderContext context)
     {
     {
-        if (end == null) return;
+        if (isexecuting) return;
+        isexecuting = true;
+        if (OutputNode == null) return;
         if (!CanExecute()) return;
         if (!CanExecute()) return;
 
 
-        var queue = CalculateExecutionQueueInternal(end);
+        var queue = CalculateExecutionQueueInternal(OutputNode);
 
 
         foreach (var node in queue)
         foreach (var node in queue)
         {
         {
-            if (node is Node typedNode)
-            {
-                if (typedNode.IsDisposed) continue;
-
-                typedNode.ExecuteInternal(context);
-            }
-            else
+            lock (node)
             {
             {
-                node.Execute(context);
+                if (node is Node typedNode)
+                {
+                    if (typedNode.IsDisposed) continue;
+
+                    typedNode.ExecuteInternal(context);
+                }
+                else
+                {
+                    node.Execute(context);
+                }
             }
             }
         }
         }
-    }
 
 
-    public void Execute(RenderContext context) => Execute(OutputNode, context);
+        isexecuting = false;
+    }
 
 
     private bool CanExecute()
     private bool CanExecute()
     {
     {
@@ -155,7 +205,8 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
 
 
         foreach (var node in queue)
         foreach (var node in queue)
         {
         {
-            hash.Add(node.GetCacheHash());
+            int nodeCache = node.GetCacheHash();
+            hash.Add(nodeCache);
         }
         }
 
 
         return hash.ToHashCode();
         return hash.ToHashCode();

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

@@ -6,5 +6,6 @@ public enum CacheTriggerFlags
     None = 0,
     None = 0,
     Inputs = 1,
     Inputs = 1,
     Timeline = 2,
     Timeline = 2,
-    All = Inputs | Timeline
+    RenderSize = 4,
+    All = Inputs | Timeline | RenderSize
 }
 }

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

@@ -82,9 +82,10 @@ public class CombineChannelsNode : RenderNode
         surface.Canvas.RestoreToCount(saved);
         surface.Canvas.RestoreToCount(saved);
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName = "")
     {
     {
-        RectD? redBounds = PreviewUtils.FindPreviewBounds(Red.Connection, frame, elementToRenderName);
+        int frame = ctx.FrameTime.Frame;
+        /*RectD? redBounds = PreviewUtils.FindPreviewBounds(Red.Connection, frame, elementToRenderName);
         RectD? greenBounds = PreviewUtils.FindPreviewBounds(Green.Connection, frame, elementToRenderName);
         RectD? greenBounds = PreviewUtils.FindPreviewBounds(Green.Connection, frame, elementToRenderName);
         RectD? blueBounds = PreviewUtils.FindPreviewBounds(Blue.Connection, frame, elementToRenderName);
         RectD? blueBounds = PreviewUtils.FindPreviewBounds(Blue.Connection, frame, elementToRenderName);
         RectD? alphaBounds = PreviewUtils.FindPreviewBounds(Alpha.Connection, frame, elementToRenderName);
         RectD? alphaBounds = PreviewUtils.FindPreviewBounds(Alpha.Connection, frame, elementToRenderName);
@@ -116,19 +117,18 @@ public class CombineChannelsNode : RenderNode
             finalBounds = finalBounds?.Union(alphaBounds.Value) ?? alphaBounds.Value;
             finalBounds = finalBounds?.Union(alphaBounds.Value) ?? alphaBounds.Value;
         }
         }
         
         
-        return finalBounds;
+        return finalBounds;*/
+        return null;
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    protected override bool ShouldRenderPreview(string elementToRenderName)
     {
     {
-        if (Red.Value == null && Green.Value == null && Blue.Value == null && Alpha.Value == null)
-        {
-            return false;
-        }
+        return Red.Value != null || Green.Value != null || Blue.Value != null || Alpha.Value != null;
+    }
 
 
-        OnPaint(context, renderOn); 
-        
-        return true;
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    {
+        OnPaint(context, renderOn);
     }
     }
 
 
     public override Node CreateCopy() => new CombineChannelsNode();
     public override Node CreateCopy() => new CombineChannelsNode();

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

@@ -10,7 +10,7 @@ using Drawie.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.CombineSeparate;
 
 
 [NodeInfo("SeparateChannels")]
 [NodeInfo("SeparateChannels")]
-public class SeparateChannelsNode : Node, IRenderInput, IPreviewRenderable
+public class SeparateChannelsNode : Node, IRenderInput
 {
 {
     private readonly Paint _paint = new();
     private readonly Paint _paint = new();
     
     
@@ -96,8 +96,7 @@ public class SeparateChannelsNode : Node, IRenderInput, IPreviewRenderable
     RenderInputProperty IRenderInput.Background => Image;
     RenderInputProperty IRenderInput.Background => Image;
     public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
     public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
     {
     {
-        RectD? bounds = PreviewUtils.FindPreviewBounds(Image.Connection, frame, elementToRenderName);
-        return bounds;
+        return null;
     }
     }
 
 
     public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)

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

@@ -13,7 +13,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
 [NodeInfo("CreateImage")]
 [NodeInfo("CreateImage")]
-public class CreateImageNode : Node, IPreviewRenderable
+public class CreateImageNode : Node
 {
 {
     public OutputProperty<Texture> Output { get; }
     public OutputProperty<Texture> Output { get; }
 
 
@@ -30,6 +30,8 @@ public class CreateImageNode : Node, IPreviewRenderable
 
 
     private TextureCache textureCache = new();
     private TextureCache textureCache = new();
 
 
+    protected override bool ExecuteOnlyOnCacheChange => true;
+    protected override CacheTriggerFlags CacheTrigger => CacheTriggerFlags.Inputs;
 
 
     public CreateImageNode()
     public CreateImageNode()
     {
     {
@@ -49,6 +51,7 @@ public class CreateImageNode : Node, IPreviewRenderable
         }
         }
 
 
         var surface = Render(context);
         var surface = Render(context);
+        RenderPreviews(surface, context);
 
 
         Output.Value = surface;
         Output.Value = surface;
 
 
@@ -57,7 +60,8 @@ public class CreateImageNode : Node, IPreviewRenderable
 
 
     private Texture Render(RenderContext context)
     private Texture Render(RenderContext context)
     {
     {
-        var surface = textureCache.RequestTexture(0, (VecI)(Size.Value * context.ChunkResolution.Multiplier()), context.ProcessingColorSpace, false);
+        int id = (Size.Value * context.ChunkResolution.Multiplier()).GetHashCode();
+        var surface = textureCache.RequestTexture(id, (VecI)(Size.Value * context.ChunkResolution.Multiplier()), context.ProcessingColorSpace, false);
         surface.DrawingSurface.Canvas.SetMatrix(Matrix3X3.Identity);
         surface.DrawingSurface.Canvas.SetMatrix(Matrix3X3.Identity);
 
 
         if (Fill.Value is ColorPaintable colorPaintable)
         if (Fill.Value is ColorPaintable colorPaintable)
@@ -100,45 +104,35 @@ public class CreateImageNode : Node, IPreviewRenderable
         surface.Canvas.RestoreToCount(saved);
         surface.Canvas.RestoreToCount(saved);
     }
     }
 
 
-    public override Node CreateCopy() => new CreateImageNode();
-
-    public override void Dispose()
+    private void RenderPreviews(Texture surface, RenderContext context)
     {
     {
-        base.Dispose();
-        textureCache.Dispose();
-    }
-
-    public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
-    {
-        if (Size.Value.X <= 0 || Size.Value.Y <= 0)
+        var previews = context.GetPreviewTexturesForNode(Id);
+        if (previews is null) return;
+        foreach (var request in previews)
         {
         {
-            return null;
-        }
+            var texture = request.Texture;
+            if (texture is null) continue;
 
 
-        return new RectD(0, 0, Size.Value.X, Size.Value.Y);
-    }
+            int saved = texture.DrawingSurface.Canvas.Save();
 
 
-    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
-    {
-        if (Size.Value.X <= 0 || Size.Value.Y <= 0)
-        {
-            return false;
-        }
+            VecD scaling = PreviewUtility.CalculateUniformScaling(surface.Size, texture.Size);
+            VecD offset = PreviewUtility.CalculateCenteringOffset(surface.Size, texture.Size, scaling);
+            texture.DrawingSurface.Canvas.Translate((float)offset.X, (float)offset.Y);
+            texture.DrawingSurface.Canvas.Scale((float)scaling.X, (float)scaling.Y);
+            var previewCtx =
+                PreviewUtility.CreatePreviewContext(context, scaling, context.RenderOutputSize, texture.Size);
 
 
-        if (Output.Value == null)
-        {
-            return false;
+            texture.DrawingSurface.Canvas.Clear();
+            texture.DrawingSurface.Canvas.DrawSurface(surface.DrawingSurface, 0, 0);
+            texture.DrawingSurface.Canvas.RestoreToCount(saved);
         }
         }
+    }
 
 
-        var surface = Render(context);
-        
-        if (surface == null || surface.IsDisposed)
-        {
-            return false;
-        }
-        
-        renderOn.Canvas.DrawSurface(surface.DrawingSurface, 0, 0);
-        
-        return true;
+    public override Node CreateCopy() => new CreateImageNode();
+
+    public override void Dispose()
+    {
+        base.Dispose();
+        textureCache.Dispose();
     }
     }
 }
 }

+ 3 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs

@@ -32,7 +32,6 @@ public class OutlineNode : RenderNode, IRenderInput
     private ImageFilter filter;
     private ImageFilter filter;
 
 
     private OutlineType? lastType = null;
     private OutlineType? lastType = null;
-    private VecI lastDocumentSize;
 
 
     protected override bool ExecuteOnlyOnCacheChange => true;
     protected override bool ExecuteOnlyOnCacheChange => true;
 
 
@@ -52,7 +51,6 @@ public class OutlineNode : RenderNode, IRenderInput
     protected override void OnExecute(RenderContext context)
     protected override void OnExecute(RenderContext context)
     {
     {
         base.OnExecute(context);
         base.OnExecute(context);
-        lastDocumentSize = context.RenderOutputSize;
 
 
         Kernel finalKernel = Type.Value switch
         Kernel finalKernel = Type.Value switch
         {
         {
@@ -118,18 +116,17 @@ public class OutlineNode : RenderNode, IRenderInput
         Background?.Value?.Paint(context, surface);
         Background?.Value?.Paint(context, surface);
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName = "")
     {
     {
-        return new RectD(0, 0, lastDocumentSize.X, lastDocumentSize.Y);
+        return new RectD(0, 0, ctx.DocumentSize.X, ctx.DocumentSize.Y);
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
     {
         int saved = renderOn.Canvas.Save();
         int saved = renderOn.Canvas.Save();
         renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         OnPaint(context, renderOn);
         OnPaint(context, renderOn);
         renderOn.Canvas.RestoreToCount(saved);
         renderOn.Canvas.RestoreToCount(saved);
-        return true;
     }
     }
 
 
     public override Node CreateCopy()
     public override Node CreateCopy()

+ 155 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/PosterizationNode.cs

@@ -0,0 +1,155 @@
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.ColorSpaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Effects;
+
+[NodeInfo("Posterization")]
+public class PosterizationNode : RenderNode, IRenderInput
+{
+    public RenderInputProperty Background { get; }
+    public InputProperty<PosterizationMode> Mode { get; }
+    public InputProperty<int> Levels { get; }
+    public InputProperty<ColorSpaceType> PosterizationColorSpace { get; }
+    
+    private Paint paint;
+    private Shader shader;
+    
+    private Shader? lastImageShader;
+    private VecI lastDocumentSize;
+    
+    private string shaderCode = """
+                                 uniform shader iImage;
+                                 uniform int iMode;
+                                 uniform float iLevels;
+                                 
+                                 half posterize(half value, float levels) {
+                                     return clamp(floor(value * (levels - 1) + 0.5) / (levels - 1), 0.0, 1.0);
+                                 }
+                                 
+                                 half4 posterizeRgb(half4 color, float levels) {
+                                     return half4(
+                                         posterize(color.r, levels),
+                                         posterize(color.g, levels),
+                                         posterize(color.b, levels),
+                                         color.a
+                                     );
+                                 }
+                                 
+                                 half4 posterizeLuminance(half4 color, float levels) {
+                                     half lum = dot(color.rgb, half3(0.299, 0.587, 0.114));
+                                     half posterizedLum = posterize(lum, levels);
+                                     return half4(posterizedLum, posterizedLum, posterizedLum, color.a);
+                                 }
+                                 
+                                 half4 main(float2 uv)
+                                 {
+                                    half4 color = iImage.eval(uv);
+                                    half4 result;
+                                    
+                                    if(iMode == 0) {
+                                        result = posterizeRgb(color, iLevels);
+                                    } else if(iMode == 1) {
+                                        result = posterizeLuminance(color, iLevels);
+                                    } 
+                                    
+                                    return result;
+                                 }
+                                 """;
+
+    protected override bool ExecuteOnlyOnCacheChange => true;
+
+    public PosterizationNode()
+    {
+        Background = CreateRenderInput("Background", "BACKGROUND");
+        Mode = CreateInput("Mode", "MODE", PosterizationMode.Rgb);
+        Levels = CreateInput("Levels", "LEVELS", 8)
+            .WithRules(v => v.Min(2).Max(256));
+        PosterizationColorSpace = CreateInput("PosterizationColorSpace", "COLOR_SPACE", ColorSpaceType.Srgb);
+        
+        paint = new Paint();
+        paint.BlendMode = BlendMode.Src;
+        Output.FirstInChain = null;
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        base.OnExecute(context);
+        lastDocumentSize = context.RenderOutputSize;
+        
+        Uniforms uniforms = new Uniforms();
+        uniforms.Add("iImage", new Uniform("iImage", lastImageShader));
+        uniforms.Add("iMode", new Uniform("iMode", (int)Mode.Value));
+        uniforms.Add("iLevels", new Uniform("iLevels", (float)Levels.Value));
+        shader?.Dispose();
+        shader = Shader.Create(shaderCode, uniforms, out _);
+    }
+    
+    protected override void OnPaint(RenderContext context, DrawingSurface surface)
+    {
+        if (Background.Value == null)
+        {
+            return;
+        }
+
+        ColorSpace colorSpace;
+        switch (PosterizationColorSpace.Value)
+        {
+            case ColorSpaceType.Srgb:
+                colorSpace = ColorSpace.CreateSrgb();
+                break;
+            case ColorSpaceType.LinearSrgb:
+                colorSpace = ColorSpace.CreateSrgbLinear();
+                break;
+            case ColorSpaceType.Inherit:
+                colorSpace = context.ProcessingColorSpace;
+                break;
+            default:
+                colorSpace = ColorSpace.CreateSrgb();
+                break;
+        }
+        
+        using Texture temp = Texture.ForProcessing(surface, colorSpace);
+        Background.Value.Paint(context, temp.DrawingSurface);
+        var snapshot = temp.DrawingSurface.Snapshot();
+        
+        lastImageShader?.Dispose();
+        lastImageShader = snapshot.ToShader();
+
+        Uniforms uniforms = new Uniforms();
+        uniforms.Add("iImage", new Uniform("iImage", lastImageShader));
+        uniforms.Add("iMode", new Uniform("iMode", (int)Mode.Value));
+        uniforms.Add("iLevels", new Uniform("iLevels", (float)Levels.Value));
+        shader = shader.WithUpdatedUniforms(uniforms);
+        paint.Shader = shader;
+        snapshot.Dispose();
+        
+        var savedTemp = temp.DrawingSurface.Canvas.Save();
+        temp.DrawingSurface.Canvas.SetMatrix(Matrix3X3.Identity);
+        temp.DrawingSurface.Canvas.DrawRect(0, 0, context.RenderOutputSize.X, context.RenderOutputSize.Y, paint);
+        temp.DrawingSurface.Canvas.RestoreToCount(savedTemp);
+        
+        var saved = surface.Canvas.Save();
+        surface.Canvas.SetMatrix(Matrix3X3.Identity);
+        surface.Canvas.DrawSurface(temp.DrawingSurface, 0, 0);
+        surface.Canvas.RestoreToCount(saved);
+    }
+
+    public override Node CreateCopy()
+    {
+        return new PosterizationNode();
+    }
+}
+
+public enum PosterizationMode
+{
+    Rgb = 0,
+    Luminance = 1,
+}

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

@@ -28,6 +28,9 @@ public sealed class ApplyFilterNode : RenderNode, IRenderInput
     
     
     public InputProperty<bool> InvertMask { get; }
     public InputProperty<bool> InvertMask { get; }
 
 
+    protected override bool ExecuteOnlyOnCacheChange => true;
+    protected override CacheTriggerFlags CacheTrigger => CacheTriggerFlags.Inputs;
+
     public ApplyFilterNode()
     public ApplyFilterNode()
     {
     {
         Background = CreateRenderInput("Input", "IMAGE");
         Background = CreateRenderInput("Input", "IMAGE");
@@ -40,7 +43,7 @@ public sealed class ApplyFilterNode : RenderNode, IRenderInput
 
 
     protected override void Paint(RenderContext context, DrawingSurface surface)
     protected override void Paint(RenderContext context, DrawingSurface surface)
     {
     {
-        AllowHighDpiRendering = (Background.Connection.Node as RenderNode)?.AllowHighDpiRendering ?? true;
+        AllowHighDpiRendering = (Background.Connection?.Node as RenderNode)?.AllowHighDpiRendering ?? true;
         base.Paint(context, surface);
         base.Paint(context, surface);
     }
     }
 
 
@@ -119,8 +122,11 @@ public sealed class ApplyFilterNode : RenderNode, IRenderInput
         finalSurface.Canvas.RestoreToCount(saved);
         finalSurface.Canvas.RestoreToCount(saved);
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "") =>
-        PreviewUtils.FindPreviewBounds(Background.Connection, frame, elementToRenderName);
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName = "") =>
+        null;
+        /*
+        PreviewUtils.FindPreviewBounds(Background.Connection, ctx.FrameTime.Frame, elementToRenderName);
+        */
 
 
     public override Node CreateCopy() => new ApplyFilterNode();
     public override Node CreateCopy() => new ApplyFilterNode();
 
 

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

@@ -44,6 +44,7 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
 
 
     public override void Render(SceneObjectRenderContext sceneContext)
     public override void Render(SceneObjectRenderContext sceneContext)
     {
     {
+        RenderPreviews(sceneContext);
         if (!IsVisible.Value || Opacity.Value <= 0 || IsEmptyMask())
         if (!IsVisible.Value || Opacity.Value <= 0 || IsEmptyMask())
         {
         {
             Output.Value = Background.Value;
             Output.Value = Background.Value;
@@ -99,8 +100,13 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
 
 
         Content.Value?.Paint(sceneContext, outputWorkingSurface.DrawingSurface);
         Content.Value?.Paint(sceneContext, outputWorkingSurface.DrawingSurface);
 
 
+        int saved2 = outputWorkingSurface.DrawingSurface.Canvas.Save();
+        outputWorkingSurface.DrawingSurface.Canvas.Scale((float)sceneContext.ChunkResolution.InvertedMultiplier());
+
         ApplyMaskIfPresent(outputWorkingSurface.DrawingSurface, sceneContext, sceneContext.ChunkResolution);
         ApplyMaskIfPresent(outputWorkingSurface.DrawingSurface, sceneContext, sceneContext.ChunkResolution);
 
 
+        outputWorkingSurface.DrawingSurface.Canvas.RestoreToCount(saved2);
+
         if (Background.Value != null && sceneContext.TargetPropertyOutput != RawOutput)
         if (Background.Value != null && sceneContext.TargetPropertyOutput != RawOutput)
         {
         {
             Texture tempSurface = RequestTexture(1, outputWorkingSurface.Size, sceneContext.ProcessingColorSpace);
             Texture tempSurface = RequestTexture(1, outputWorkingSurface.Size, sceneContext.ProcessingColorSpace);
@@ -238,44 +244,37 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
         return guids;
         return guids;
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementFor = "")
+    protected override bool ShouldRenderPreview(string elementToRenderName)
     {
     {
-        if (elementFor == nameof(EmbeddedMask))
+        if (elementToRenderName == nameof(EmbeddedMask))
         {
         {
-            return base.GetPreviewBounds(frame, elementFor);
+            return base.ShouldRenderPreview(elementToRenderName);
         }
         }
 
 
-        return GetApproxBounds(frame);
+        return Content.Connection != null;
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context,
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName)
+    {
+        return GetApproxBounds(ctx.FrameTime);
+    }
+
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
         string elementToRenderName)
     {
     {
         if (elementToRenderName == nameof(EmbeddedMask))
         if (elementToRenderName == nameof(EmbeddedMask))
         {
         {
-            return base.RenderPreview(renderOn, context, elementToRenderName);
+            base.RenderPreview(renderOn, context, elementToRenderName);
+            return;
         }
         }
 
 
         if (Content.Connection != null)
         if (Content.Connection != null)
         {
         {
-            var executionQueue = GraphUtils.CalculateExecutionQueue(Content.Connection.Node, FilterInvisibleFolders);
-            while (executionQueue.Count > 0)
+            if (context is SceneObjectRenderContext ctx)
             {
             {
-                IReadOnlyNode node = executionQueue.Dequeue();
-
-                if (node is IReadOnlyStructureNode { IsVisible.Value: false })
-                {
-                    continue;
-                }
-
-                if (node is IPreviewRenderable previewRenderable)
-                {
-                    previewRenderable.RenderPreview(renderOn, context, elementToRenderName);
-                }
+                RenderFolderContent(ctx, true);
             }
             }
         }
         }
-
-        return true;
     }
     }
 
 
     void IClipSource.DrawClipSource(SceneObjectRenderContext context, DrawingSurface drawOnto)
     void IClipSource.DrawClipSource(SceneObjectRenderContext context, DrawingSurface drawOnto)

+ 65 - 83
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Animations;
+using System.Diagnostics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Helpers;
@@ -28,10 +29,9 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
 
     private VecI startSize;
     private VecI startSize;
     private ColorSpace colorSpace;
     private ColorSpace colorSpace;
-    private ChunkyImage layerImage => keyFrames[0]?.Data as ChunkyImage;
 
 
-    private Texture fullResrenderedSurface;
-    private int renderedSurfaceFrame = -1;
+
+    private ChunkyImage layerImage => keyFrames[0]?.Data as ChunkyImage;
 
 
     public ImageLayerNode(VecI size, ColorSpace colorSpace)
     public ImageLayerNode(VecI size, ColorSpace colorSpace)
     {
     {
@@ -116,49 +116,53 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
     protected override void DrawWithoutFilters(SceneObjectRenderContext ctx, DrawingSurface workingSurface,
     protected override void DrawWithoutFilters(SceneObjectRenderContext ctx, DrawingSurface workingSurface,
         Paint paint)
         Paint paint)
     {
     {
-        DrawLayer(workingSurface, paint, ctx);
+        DrawLayer(workingSurface, paint, ctx, false);
     }
     }
 
 
     protected override void DrawWithFilters(SceneObjectRenderContext context, DrawingSurface workingSurface,
     protected override void DrawWithFilters(SceneObjectRenderContext context, DrawingSurface workingSurface,
         Paint paint)
         Paint paint)
     {
     {
-        DrawLayer(workingSurface, paint, context);
+        DrawLayer(workingSurface, paint, context, true);
     }
     }
 
 
-    private void DrawLayer(DrawingSurface workingSurface, Paint paint, SceneObjectRenderContext ctx)
+    private void DrawLayer(DrawingSurface workingSurface, Paint paint, SceneObjectRenderContext ctx, bool saveLayer)
     {
     {
         int saved = workingSurface.Canvas.Save();
         int saved = workingSurface.Canvas.Save();
 
 
         var sceneSize = GetSceneSize(ctx.FrameTime);
         var sceneSize = GetSceneSize(ctx.FrameTime);
-        VecD topLeft = sceneSize / 2f;
+        RectI latestSize = new(0, 0, layerImage.LatestSize.X, layerImage.LatestSize.Y);
+        var region = ctx.VisibleDocumentRegion ?? latestSize;
+
+        VecD topLeft = region.TopLeft - sceneSize / 2;
 
 
-        if (renderedSurfaceFrame == null || ctx.FullRerender || ctx.FrameTime.Frame != renderedSurfaceFrame)
+        topLeft *= ctx.ChunkResolution.Multiplier();
+        workingSurface.Canvas.Scale((float)ctx.ChunkResolution.InvertedMultiplier());
+        var img = GetLayerImageAtFrame(ctx.FrameTime.Frame);
+
+        if (saveLayer)
         {
         {
-            GetLayerImageAtFrame(ctx.FrameTime.Frame).DrawMostUpToDateRegionOn(
-                new RectI(0, 0, layerImage.LatestSize.X, layerImage.LatestSize.Y),
-                ChunkResolution.Full,
-                workingSurface, -topLeft, paint);
+            workingSurface.Canvas.SaveLayer(paint);
+        }
+
+        if (!ctx.FullRerender)
+        {
+            img.DrawMostUpToDateRegionOnWithAffected(
+                region,
+                ctx.ChunkResolution,
+                workingSurface, ctx.AffectedArea, topLeft, saveLayer ? null : paint, ctx.DesiredSamplingOptions);
         }
         }
         else
         else
         {
         {
-            if (ctx.DesiredSamplingOptions == SamplingOptions.Default)
-            {
-                workingSurface.Canvas.DrawSurface(
-                    fullResrenderedSurface.DrawingSurface, -(float)topLeft.X, -(float)topLeft.Y, paint);
-            }
-            else
-            {
-                using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
-                workingSurface.Canvas.DrawImage(snapshot, -(float)topLeft.X, -(float)topLeft.Y,
-                    ctx.DesiredSamplingOptions,
-                    paint);
-            }
+            img.DrawMostUpToDateRegionOn(
+                region,
+                ctx.ChunkResolution,
+                workingSurface, topLeft, saveLayer ? null : paint, ctx.DesiredSamplingOptions);
         }
         }
 
 
         workingSurface.Canvas.RestoreToCount(saved);
         workingSurface.Canvas.RestoreToCount(saved);
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementFor = "")
+    public override RectD? GetPreviewBounds(RenderContext context, string elementFor = "")
     {
     {
         if (IsDisposed)
         if (IsDisposed)
         {
         {
@@ -167,11 +171,16 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
 
         if (elementFor == nameof(EmbeddedMask))
         if (elementFor == nameof(EmbeddedMask))
         {
         {
-            return base.GetPreviewBounds(frame, elementFor);
+            return base.GetPreviewBounds(context, elementFor);
         }
         }
 
 
         if (Guid.TryParse(elementFor, out Guid guid))
         if (Guid.TryParse(elementFor, out Guid guid))
         {
         {
+            if (guid == Id)
+            {
+                return new RectD(0, 0, layerImage.CommittedSize.X, layerImage.CommittedSize.Y);
+            }
+
             var keyFrame = keyFrames.FirstOrDefault(x => x.KeyFrameGuid == guid);
             var keyFrame = keyFrames.FirstOrDefault(x => x.KeyFrameGuid == guid);
 
 
             if (keyFrame != null)
             if (keyFrame != null)
@@ -182,19 +191,13 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
                     return null;
                     return null;
                 }
                 }
 
 
-                RectI? bounds = (RectI?)GetApproxBounds(kf);
-                if (bounds.HasValue)
-                {
-                    return new RectD(bounds.Value.X, bounds.Value.Y,
-                        Math.Min(bounds.Value.Width, kf.CommittedSize.X),
-                        Math.Min(bounds.Value.Height, kf.CommittedSize.Y));
-                }
+                return new RectD(0, 0, kf.CommittedSize.X, kf.CommittedSize.Y);
             }
             }
         }
         }
 
 
         try
         try
         {
         {
-            var kf = GetLayerImageAtFrame(frame);
+            var kf = GetLayerImageAtFrame(context.FrameTime.Frame);
             if (kf == null)
             if (kf == null)
             {
             {
                 return null;
                 return null;
@@ -216,8 +219,7 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         }
         }
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOnto, RenderContext context,
-        string elementToRenderName)
+    protected override bool ShouldRenderPreview(string elementToRenderName)
     {
     {
         if (IsDisposed)
         if (IsDisposed)
         {
         {
@@ -226,12 +228,28 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
 
         if (elementToRenderName == nameof(EmbeddedMask))
         if (elementToRenderName == nameof(EmbeddedMask))
         {
         {
-            return base.RenderPreview(renderOnto, context, elementToRenderName);
+            return base.ShouldRenderPreview(elementToRenderName);
+        }
+
+        return true;
+    }
+
+    public override void RenderPreview(DrawingSurface renderOnto, RenderContext context,
+        string elementToRenderName)
+    {
+        if (IsDisposed)
+        {
+            return;
+        }
+
+        if (elementToRenderName == nameof(EmbeddedMask))
+        {
+            base.RenderPreview(renderOnto, context, elementToRenderName);
+            return;
         }
         }
 
 
         var img = GetLayerImageAtFrame(context.FrameTime.Frame);
         var img = GetLayerImageAtFrame(context.FrameTime.Frame);
 
 
-        int cacheFrame = context.FrameTime.Frame;
         if (Guid.TryParse(elementToRenderName, out Guid guid))
         if (Guid.TryParse(elementToRenderName, out Guid guid))
         {
         {
             var keyFrame = keyFrames.FirstOrDefault(x => x.KeyFrameGuid == guid);
             var keyFrame = keyFrames.FirstOrDefault(x => x.KeyFrameGuid == guid);
@@ -239,46 +257,27 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
             if (keyFrame != null)
             if (keyFrame != null)
             {
             {
                 img = GetLayerImageByKeyFrameGuid(keyFrame.KeyFrameGuid);
                 img = GetLayerImageByKeyFrameGuid(keyFrame.KeyFrameGuid);
-                cacheFrame = keyFrame.StartFrame;
             }
             }
             else if (guid == Id)
             else if (guid == Id)
             {
             {
                 img = GetLayerImageAtFrame(0);
                 img = GetLayerImageAtFrame(0);
-                cacheFrame = 0;
             }
             }
         }
         }
 
 
         if (img is null)
         if (img is null)
         {
         {
-            return false;
+            return;
         }
         }
 
 
-        if (renderedSurfaceFrame == cacheFrame)
-        {
-            int saved = renderOnto.Canvas.Save();
-            renderOnto.Canvas.Scale((float)context.ChunkResolution.Multiplier());
-            if (context.DesiredSamplingOptions == SamplingOptions.Default)
-            {
-                renderOnto.Canvas.DrawSurface(
-                    fullResrenderedSurface.DrawingSurface, 0, 0, blendPaint);
-            }
-            else
-            {
-                using var snapshot = fullResrenderedSurface.DrawingSurface.Snapshot();
-                renderOnto.Canvas.DrawImage(snapshot, 0, 0, context.DesiredSamplingOptions, blendPaint);
-            }
+        int saved = renderOnto.Canvas.Save();
+        renderOnto.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
 
 
-            renderOnto.Canvas.RestoreToCount(saved);
-        }
-        else
-        {
-            img.DrawMostUpToDateRegionOn(
-                new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
-                context.ChunkResolution,
-                renderOnto, VecI.Zero, blendPaint, context.DesiredSamplingOptions);
-        }
+        img.DrawCommittedRegionOn(
+            new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
+            context.ChunkResolution,
+            renderOnto, VecI.Zero, replacePaint, context.DesiredSamplingOptions);
 
 
-        return true;
+        renderOnto.Canvas.RestoreToCount(saved);
     }
     }
 
 
     private KeyFrameData GetFrameWithImage(KeyFrameTime frame)
     private KeyFrameData GetFrameWithImage(KeyFrameTime frame)
@@ -330,12 +329,6 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
         return image;
         return image;
     }
     }
 
 
-    public override void Dispose()
-    {
-        base.Dispose();
-        fullResrenderedSurface?.Dispose();
-    }
-
     IReadOnlyChunkyImage IReadOnlyImageNode.GetLayerImageAtFrame(int frame) => GetLayerImageAtFrame(frame);
     IReadOnlyChunkyImage IReadOnlyImageNode.GetLayerImageAtFrame(int frame) => GetLayerImageAtFrame(frame);
 
 
     IReadOnlyChunkyImage IReadOnlyImageNode.GetLayerImageByKeyFrameGuid(Guid keyFrameGuid) =>
     IReadOnlyChunkyImage IReadOnlyImageNode.GetLayerImageByKeyFrameGuid(Guid keyFrameGuid) =>
@@ -346,17 +339,6 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
 
     void IReadOnlyImageNode.ForEveryFrame(Action<IReadOnlyChunkyImage> action) => ForEveryFrame(action);
     void IReadOnlyImageNode.ForEveryFrame(Action<IReadOnlyChunkyImage> action) => ForEveryFrame(action);
 
 
-    public override void RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime,
-        ColorSpace processColorSpace)
-    {
-        base.RenderChunk(chunkPos, resolution, frameTime, processColorSpace);
-
-        var img = GetLayerImageAtFrame(frameTime.Frame);
-
-        RenderChunkyImageChunk(chunkPos, resolution, img, 85, processColorSpace, ref fullResrenderedSurface);
-        renderedSurfaceFrame = frameTime.Frame;
-    }
-
     public void ForEveryFrame(Action<ChunkyImage> action)
     public void ForEveryFrame(Action<ChunkyImage> action)
     {
     {
         foreach (var frame in keyFrames)
         foreach (var frame in keyFrames)

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

@@ -22,6 +22,7 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
 
 
     public override void Render(SceneObjectRenderContext sceneContext)
     public override void Render(SceneObjectRenderContext sceneContext)
     {
     {
+        RenderPreviews(sceneContext);
         if (!IsVisible.Value || Opacity.Value <= 0 || IsEmptyMask())
         if (!IsVisible.Value || Opacity.Value <= 0 || IsEmptyMask())
         {
         {
             Output.Value = Background.Value;
             Output.Value = Background.Value;
@@ -44,6 +45,7 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
                 blendPaint.BlendMode = RenderContext.GetDrawingBlendMode(BlendMode.Value);
                 blendPaint.BlendMode = RenderContext.GetDrawingBlendMode(BlendMode.Value);
             }
             }
 
 
+            // TODO: Optimizattion: Simple graphs can draw directly to scene, skipping the intermediate surface
             if (AllowHighDpiRendering || renderOnto.DeviceClipBounds.Size == context.RenderOutputSize)
             if (AllowHighDpiRendering || renderOnto.DeviceClipBounds.Size == context.RenderOutputSize)
             {
             {
                 DrawLayerInScene(context, renderOnto, useFilters);
                 DrawLayerInScene(context, renderOnto, useFilters);
@@ -59,7 +61,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
                 var tempSurface = TryInitWorkingSurface(context.RenderOutputSize, context.ChunkResolution,
                 var tempSurface = TryInitWorkingSurface(context.RenderOutputSize, context.ChunkResolution,
                     context.ProcessingColorSpace, 22);
                     context.ProcessingColorSpace, 22);
 
 
-                DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters, targetPaint);
+                DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters,
+                    targetPaint);
 
 
                 blendPaint.SetFilters(null);
                 blendPaint.SetFilters(null);
                 DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution,
                 DrawWithResolution(tempSurface.DrawingSurface, renderOnto, context.ChunkResolution,
@@ -151,7 +154,8 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
         workingSurface.Canvas.RestoreToCount(scaled);
         workingSurface.Canvas.RestoreToCount(scaled);
     }
     }
 
 
-    private void DrawWithResolution(DrawingSurface source, DrawingSurface target, ChunkResolution resolution, SamplingOptions sampling)
+    private void DrawWithResolution(DrawingSurface source, DrawingSurface target, ChunkResolution resolution,
+        SamplingOptions sampling)
     {
     {
         int scaled = target.Canvas.Save();
         int scaled = target.Canvas.Save();
         float multiplier = (float)resolution.InvertedMultiplier();
         float multiplier = (float)resolution.InvertedMultiplier();

+ 15 - 9
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Matrix/Matrix3X3BaseNode.cs

@@ -56,30 +56,36 @@ public abstract class Matrix3X3BaseNode : RenderNode, IRenderInput
 
 
         Float3x3 mtx = Matrix.Value.Invoke(FuncContext.NoContext);
         Float3x3 mtx = Matrix.Value.Invoke(FuncContext.NoContext);
 
 
+        Matrix3X3 constant = mtx.GetConstant() as Matrix3X3? ?? Matrix3X3.Identity;
         surface.Canvas.SetMatrix(
         surface.Canvas.SetMatrix(
-            surface.Canvas.TotalMatrix.Concat(mtx.GetConstant() as Matrix3X3? ?? Matrix3X3.Identity));
+            surface.Canvas.TotalMatrix.Concat(constant));
+
+        var clonedCtx = context.Clone();
+        if (clonedCtx.VisibleDocumentRegion.HasValue)
+        {
+            clonedCtx.VisibleDocumentRegion =
+                (RectI)constant.Invert().TransformRect((RectD)clonedCtx.VisibleDocumentRegion.Value);
+        }
+
         if (!surface.LocalClipBounds.IsZeroOrNegativeArea)
         if (!surface.LocalClipBounds.IsZeroOrNegativeArea)
         {
         {
-            Background.Value?.Paint(context, surface);
+            Background.Value?.Paint(clonedCtx, surface);
         }
         }
 
 
         surface.Canvas.RestoreToCount(layer);
         surface.Canvas.RestoreToCount(layer);
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName = "")
     {
     {
         if (Background.Value == null)
         if (Background.Value == null)
             return null;
             return null;
 
 
-        return base.GetPreviewBounds(frame, elementToRenderName);
+        return base.GetPreviewBounds(ctx, elementToRenderName);
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    protected override bool ShouldRenderPreview(string elementToRenderName)
     {
     {
-        if (Background.Value == null)
-            return false;
-
-        return base.RenderPreview(renderOn, context, elementToRenderName);
+        return Background.Value != null;
     }
     }
 
 
     protected abstract Float3x3 CalculateMatrix(FuncContext ctx, Float3x3 input);
     protected abstract Float3x3 CalculateMatrix(FuncContext ctx, Float3x3 input);

+ 4 - 31
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs

@@ -70,46 +70,19 @@ public class MergeNode : RenderNode
         Top.Value?.Paint(context, target);
         Top.Value?.Paint(context, target);
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    protected override bool ShouldRenderPreview(string elementToRenderName)
     {
     {
-        if (Top.Value == null && Bottom.Value == null)
-        {
-            return null;
-        }
-
-        RectD? totalBounds = null;
-
-        if (Top.Connection != null && Top.Connection.Node is IPreviewRenderable topPreview)
-        {
-            var topBounds = topPreview.GetPreviewBounds(frame, elementToRenderName);
-            if (topBounds != null)
-            {
-                totalBounds = totalBounds?.Union(topBounds.Value) ?? topBounds;
-            }
-        }
-
-        if (Bottom.Connection != null && Bottom.Connection.Node is IPreviewRenderable bottomPreview)
-        {
-            var bottomBounds = bottomPreview.GetPreviewBounds(frame, elementToRenderName);
-            if (bottomBounds != null)
-            {
-                totalBounds = totalBounds?.Union(bottomBounds.Value) ?? bottomBounds;
-            }
-        }
-
-        return totalBounds;
+        return Top.Value != null || Bottom.Value != null;
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
     {
         if (Top.Value == null && Bottom.Value == null)
         if (Top.Value == null && Bottom.Value == null)
         {
         {
-            return false;
+            return;
         }
         }
 
 
         Merge(renderOn, context);
         Merge(renderOn, context);
-
-        return true;
     }
     }
 
 
     public override void Dispose()
     public override void Dispose()

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

@@ -12,7 +12,7 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
 [NodeInfo("ModifyImageLeft")]
 [NodeInfo("ModifyImageLeft")]
 [PairNode(typeof(ModifyImageRightNode), "ModifyImageZone", true)]
 [PairNode(typeof(ModifyImageRightNode), "ModifyImageZone", true)]
-public class ModifyImageLeftNode : Node, IPairNode, IPreviewRenderable
+public class ModifyImageLeftNode : Node, IPairNode
 {
 {
     public InputProperty<Texture?> Image { get; }
     public InputProperty<Texture?> Image { get; }
 
 
@@ -48,20 +48,37 @@ public class ModifyImageLeftNode : Node, IPairNode, IPreviewRenderable
 
 
     protected override void OnExecute(RenderContext context)
     protected override void OnExecute(RenderContext context)
     {
     {
+        RenderPreviews(context);
     }
     }
 
 
     public override Node CreateCopy() => new ModifyImageLeftNode();
     public override Node CreateCopy() => new ModifyImageLeftNode();
-    public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+
+    private void RenderPreviews(RenderContext context)
     {
     {
-        if(Image.Value == null)
+        var previews = context.GetPreviewTexturesForNode(Id);
+        if (previews is null) return;
+        foreach (var request in previews)
         {
         {
-            return null;
-        } 
-        
-        return new RectD(0, 0, Image.Value.Size.X, Image.Value.Size.Y);
+            var texture = request.Texture;
+            if (texture is null) continue;
+
+            int saved = texture.DrawingSurface.Canvas.Save();
+
+            VecI size = Image.Value?.Size ?? context.RenderOutputSize;
+            VecD scaling = PreviewUtility.CalculateUniformScaling(size, texture.Size);
+            VecD offset = PreviewUtility.CalculateCenteringOffset(size, texture.Size, scaling);
+            texture.DrawingSurface.Canvas.Translate((float)offset.X, (float)offset.Y);
+            texture.DrawingSurface.Canvas.Scale((float)scaling.X, (float)scaling.Y);
+            var previewCtx =
+                PreviewUtility.CreatePreviewContext(context, scaling, context.RenderOutputSize, texture.Size);
+
+            texture.DrawingSurface.Canvas.Clear();
+            RenderPreview(texture.DrawingSurface);
+            texture.DrawingSurface.Canvas.RestoreToCount(saved);
+        }
     }
     }
 
 
-    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    public bool RenderPreview(DrawingSurface renderOn)
     {
     {
         if(Image.Value is null)
         if(Image.Value is null)
         {
         {

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

@@ -2,6 +2,7 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Backend.Core.Shaders.Generation.Expressions;
 using Drawie.Backend.Core.Shaders.Generation.Expressions;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
@@ -114,28 +115,21 @@ public class ModifyImageRightNode : RenderNode, IPairNode, ICustomShaderNode
         builder.Dispose();
         builder.Dispose();
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName)
     {
     {
-        var startNode = FindStartNode();
-        if (startNode != null)
-        {
-            return startNode.GetPreviewBounds(frame, elementToRenderName);
-        }
-
-        return null;
+        return size.HasValue ? new RectD(0, 0, size.Value.X, size.Value.Y) : null;
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
     {
         var startNode = FindStartNode();
         var startNode = FindStartNode();
         if (drawingPaint != null && startNode is { Image.Value: not null })
         if (drawingPaint != null && startNode is { Image.Value: not null })
         {
         {
-            renderOn.Canvas.DrawRect(0, 0, startNode.Image.Value.Size.X, startNode.Image.Value.Size.Y, drawingPaint);
+            using var tmpTex = Texture.ForProcessing(startNode.Image.Value.Size, context.ProcessingColorSpace);
 
 
-            return true;
+            tmpTex.DrawingSurface.Canvas.DrawRect(0, 0, startNode.Image.Value.Size.X, startNode.Image.Value.Size.Y, drawingPaint);
+            renderOn.Canvas.DrawSurface(tmpTex.DrawingSurface, 0, 0);
         }
         }
-
-        return false;
     }
     }
 
 
     public override void Dispose()
     public override void Dispose()

+ 12 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -12,6 +12,7 @@ using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.FilterNodes;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
@@ -47,7 +48,7 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
 
     private VecI lastRenderSize = new VecI(0, 0);
     private VecI lastRenderSize = new VecI(0, 0);
 
 
-    protected internal bool IsDisposed => _isDisposed;
+    public bool IsDisposed => _isDisposed;
     private bool _isDisposed;
     private bool _isDisposed;
 
 
     private int lastContentCacheHash = -1;
     private int lastContentCacheHash = -1;
@@ -83,13 +84,18 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
 
     protected virtual bool CacheChanged(RenderContext context)
     protected virtual bool CacheChanged(RenderContext context)
     {
     {
-        bool changed = lastRenderSize != context.RenderOutputSize;
+        bool changed = false;
 
 
         if (CacheTrigger.HasFlag(CacheTriggerFlags.Inputs))
         if (CacheTrigger.HasFlag(CacheTriggerFlags.Inputs))
         {
         {
             changed |= inputs.Any(x => x.CacheChanged);
             changed |= inputs.Any(x => x.CacheChanged);
         }
         }
 
 
+        if (CacheTrigger.HasFlag(CacheTriggerFlags.RenderSize))
+        {
+            changed |= lastRenderSize != context.RenderOutputSize;
+        }
+
         if (CacheTrigger.HasFlag(CacheTriggerFlags.Timeline))
         if (CacheTrigger.HasFlag(CacheTriggerFlags.Timeline))
         {
         {
             changed |= lastFrameTime.Frame != context.FrameTime.Frame ||
             changed |= lastFrameTime.Frame != context.FrameTime.Frame ||
@@ -117,7 +123,8 @@ public abstract class Node : IReadOnlyNode, IDisposable
         lastContentCacheHash = GetContentCacheHash();
         lastContentCacheHash = GetContentCacheHash();
     }
     }
 
 
-    public void TraverseBackwards(Func<IReadOnlyNode, IInputProperty, bool> action, Func<IInputProperty, bool>? branchCondition = null)
+    public void TraverseBackwards(Func<IReadOnlyNode, IInputProperty, bool> action,
+        Func<IInputProperty, bool>? branchCondition = null)
     {
     {
         var visited = new HashSet<IReadOnlyNode>();
         var visited = new HashSet<IReadOnlyNode>();
         var queueNodes = new Queue<(IReadOnlyNode, IInputProperty)>();
         var queueNodes = new Queue<(IReadOnlyNode, IInputProperty)>();
@@ -143,6 +150,7 @@ public abstract class Node : IReadOnlyNode, IDisposable
                 {
                 {
                     continue;
                     continue;
                 }
                 }
+
                 if (inputProperty.Connection != null)
                 if (inputProperty.Connection != null)
                 {
                 {
                     queueNodes.Enqueue((inputProperty.Connection.Node, inputProperty));
                     queueNodes.Enqueue((inputProperty.Connection.Node, inputProperty));
@@ -613,6 +621,7 @@ public abstract class Node : IReadOnlyNode, IDisposable
         hash.Add(GetType());
         hash.Add(GetType());
         hash.Add(DisplayName);
         hash.Add(DisplayName);
         hash.Add(Position);
         hash.Add(Position);
+
         foreach (var input in inputs)
         foreach (var input in inputs)
         {
         {
             hash.Add(input.GetCacheHash());
             hash.Add(input.GetCacheHash());

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

@@ -117,29 +117,23 @@ public class NoiseNode : RenderNode
         workingSurface.Canvas.RestoreToCount(saved);
         workingSurface.Canvas.RestoreToCount(saved);
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
-    {
-        return new RectD(0, 0, 128, 128); 
-    }
-
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
     {
         var shader = SelectShader();
         var shader = SelectShader();
         if (shader == null)
         if (shader == null)
         {
         {
-            return false;
+            return;
         }
         }
 
 
         if (paint.Shader != voronoiShader)
         if (paint.Shader != voronoiShader)
         {
         {
             paint?.Shader?.Dispose();
             paint?.Shader?.Dispose();
         }
         }
+
         paint.Shader = shader;
         paint.Shader = shader;
         paint.ColorFilter = grayscaleFilter;
         paint.ColorFilter = grayscaleFilter;
         
         
         RenderNoise(renderOn);
         RenderNoise(renderOn);
-
-        return true;
     }
     }
 
 
     private Shader SelectShader()
     private Shader SelectShader()

+ 19 - 24
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -8,7 +8,7 @@ using Drawie.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
 [NodeInfo("Output")]
 [NodeInfo("Output")]
-public class OutputNode : Node, IRenderInput, IPreviewRenderable
+public class OutputNode : Node, IRenderInput
 {
 {
     public const string UniqueName = "PixiEditor.Output";
     public const string UniqueName = "PixiEditor.Output";
     public const string InputPropertyName = "Background";
     public const string InputPropertyName = "Background";
@@ -34,33 +34,28 @@ public class OutputNode : Node, IRenderInput, IPreviewRenderable
 
 
         Input.Value?.Paint(context, context.RenderSurface);
         Input.Value?.Paint(context, context.RenderSurface);
         lastDocumentSize = context.DocumentSize;
         lastDocumentSize = context.DocumentSize;
-    }
-
-    RenderInputProperty IRenderInput.Background => Input;
-
-    public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
-    {
-        if (lastDocumentSize == null)
-        {
-            return null;
-        }
 
 
-        return new RectD(0, 0, lastDocumentSize.Value.X, lastDocumentSize.Value.Y);
-    }
-
-    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
-    {
-        if (Input.Value == null)
+        var previews = context.GetPreviewTexturesForNode(Id);
+        if (previews is null) return;
+        foreach (var request in previews)
         {
         {
-            return false;
-        }
+            var texture = request.Texture;
+            if (texture is null) continue;
 
 
-        int saved = renderOn.Canvas.Save();
-        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
-        Input.Value.Paint(context, renderOn);
+            int saved = texture.DrawingSurface.Canvas.Save();
 
 
-        renderOn.Canvas.RestoreToCount(saved);
+            VecD scaling = PreviewUtility.CalculateUniformScaling(context.DocumentSize, texture.Size);
+            VecD offset = PreviewUtility.CalculateCenteringOffset(context.DocumentSize, texture.Size, scaling);
+            texture.DrawingSurface.Canvas.Translate((float)offset.X, (float)offset.Y);
+            texture.DrawingSurface.Canvas.Scale((float)scaling.X, (float)scaling.Y);
+            var previewCtx =
+                PreviewUtility.CreatePreviewContext(context, scaling, context.RenderOutputSize, texture.Size);
 
 
-        return true;
+            texture.DrawingSurface.Canvas.Clear();
+            Input.Value?.Paint(previewCtx, texture.DrawingSurface);
+            texture.DrawingSurface.Canvas.RestoreToCount(saved);
+        }
     }
     }
+
+    RenderInputProperty IRenderInput.Background => Input;
 }
 }

+ 57 - 12
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs

@@ -11,7 +11,7 @@ using PixiEditor.ChangeableDocument.Changes.Structure;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 
-public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
+public abstract class RenderNode : Node, IHighDpiRenderNode
 {
 {
     public RenderOutputProperty Output { get; }
     public RenderOutputProperty Output { get; }
 
 
@@ -49,10 +49,12 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
         DrawingSurface target = surface;
         DrawingSurface target = surface;
         bool useIntermediate = !AllowHighDpiRendering
         bool useIntermediate = !AllowHighDpiRendering
                                && context.RenderOutputSize is { X: > 0, Y: > 0 }
                                && context.RenderOutputSize is { X: > 0, Y: > 0 }
-                               && (surface.DeviceClipBounds.Size != context.RenderOutputSize || (RendersInAbsoluteCoordinates && !surface.Canvas.TotalMatrix.IsIdentity));
+                               && (surface.DeviceClipBounds.Size != context.RenderOutputSize ||
+                                   (RendersInAbsoluteCoordinates && !surface.Canvas.TotalMatrix.IsIdentity));
         if (useIntermediate)
         if (useIntermediate)
         {
         {
-            Texture intermediate = textureCache.RequestTexture(-6451, context.RenderOutputSize, context.ProcessingColorSpace);
+            Texture intermediate =
+                textureCache.RequestTexture(-6451, context.RenderOutputSize, context.ProcessingColorSpace);
             target = intermediate.DrawingSurface;
             target = intermediate.DrawingSurface;
         }
         }
 
 
@@ -81,22 +83,66 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
                 surface.Canvas.Restore();
                 surface.Canvas.Restore();
             }
             }
         }
         }
+
+        RenderPreviews(context);
     }
     }
 
 
     protected abstract void OnPaint(RenderContext context, DrawingSurface surface);
     protected abstract void OnPaint(RenderContext context, DrawingSurface surface);
 
 
-    public virtual RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    protected void RenderPreviews(RenderContext ctx)
     {
     {
-        return new RectD(0, 0, lastDocumentSize.X, lastDocumentSize.Y);
+        var previewToRender = ctx.GetPreviewTexturesForNode(Id);
+        if (previewToRender == null || previewToRender.Count == 0)
+            return;
+
+        foreach (var preview in previewToRender)
+        {
+            if (!ShouldRenderPreview(preview.ElementToRender))
+                continue;
+
+            if (preview.Texture == null)
+                continue;
+
+            int saved = preview.Texture.DrawingSurface.Canvas.Save();
+            preview.Texture.DrawingSurface.Canvas.Clear();
+
+            var bounds = GetPreviewBounds(ctx, preview.ElementToRender);
+            if (bounds == null)
+            {
+                bounds = new RectD(0, 0, ctx.RenderOutputSize.X, ctx.RenderOutputSize.Y);
+            }
+
+            VecD scaling = PreviewUtility.CalculateUniformScaling(bounds.Value.Size, preview.Texture.Size);
+            VecD offset = PreviewUtility.CalculateCenteringOffset(bounds.Value.Size, preview.Texture.Size, scaling);
+            RenderContext adjusted =
+                PreviewUtility.CreatePreviewContext(ctx, scaling, bounds.Value.Size, preview.Texture.Size);
+
+            preview.Texture.DrawingSurface.Canvas.Translate((float)offset.X, (float)offset.Y);
+            preview.Texture.DrawingSurface.Canvas.Scale((float)scaling.X, (float)scaling.Y);
+            preview.Texture.DrawingSurface.Canvas.Translate((float)-bounds.Value.X, (float)-bounds.Value.Y);
+
+            adjusted.RenderSurface = preview.Texture.DrawingSurface;
+            RenderPreview(preview.Texture.DrawingSurface, adjusted, preview.ElementToRender);
+            preview.Texture.DrawingSurface.Canvas.RestoreToCount(saved);
+        }
+    }
+
+    protected virtual bool ShouldRenderPreview(string elementToRenderName)
+    {
+        return true;
     }
     }
 
 
-    public virtual bool RenderPreview(DrawingSurface renderOn, RenderContext context,
+    public virtual RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName)
+    {
+        return null;
+    }
+
+    public virtual void RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
         string elementToRenderName)
     {
     {
         int saved = renderOn.Canvas.Save();
         int saved = renderOn.Canvas.Save();
         OnPaint(context, renderOn);
         OnPaint(context, renderOn);
         renderOn.Canvas.RestoreToCount(saved);
         renderOn.Canvas.RestoreToCount(saved);
-        return true;
     }
     }
 
 
     protected Texture RequestTexture(int id, VecI size, ColorSpace processingCs, bool clear = true)
     protected Texture RequestTexture(int id, VecI size, ColorSpace processingCs, bool clear = true)
@@ -110,19 +156,18 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
         additionalData["AllowHighDpiRendering"] = AllowHighDpiRendering;
         additionalData["AllowHighDpiRendering"] = AllowHighDpiRendering;
     }
     }
 
 
-    internal override void DeserializeAdditionalData(IReadOnlyDocument target, IReadOnlyDictionary<string, object> data, List<IChangeInfo> infos)
+    internal override void DeserializeAdditionalData(IReadOnlyDocument target, IReadOnlyDictionary<string, object> data,
+        List<IChangeInfo> infos)
     {
     {
         base.DeserializeAdditionalData(target, data, infos);
         base.DeserializeAdditionalData(target, data, infos);
 
 
-        if(data.TryGetValue("AllowHighDpiRendering", out var value))
+        if (data.TryGetValue("AllowHighDpiRendering", out var value))
             AllowHighDpiRendering = (bool)value;
             AllowHighDpiRendering = (bool)value;
     }
     }
 
 
     public override void Dispose()
     public override void Dispose()
     {
     {
         base.Dispose();
         base.Dispose();
-        textureCache.Dispose(); 
+        textureCache.Dispose();
     }
     }
-
-   
 }
 }

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

@@ -58,11 +58,11 @@ public class SampleImageNode : Node
 
 
         if (SampleMode.Value == ColorSampleMode.ColorManaged)
         if (SampleMode.Value == ColorSampleMode.ColorManaged)
         {
         {
-            color = Image.Value.GetSRGBPixel(pixelCoordinate);
+            color = Image.Value.GetSrgbPixel(pixelCoordinate);
         }
         }
         else
         else
         {
         {
-            color = Image.Value.GetPixel(pixelCoordinate);
+            color = Image.Value.GetRawPixel(pixelCoordinate);
         }
         }
 
 
         return new Half4("") { ConstantValue = color };
         return new Half4("") { ConstantValue = color };

+ 4 - 9
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs

@@ -24,7 +24,6 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
     private string lastShaderCode;
     private string lastShaderCode;
     private Paint paint;
     private Paint paint;
 
 
-    private VecI lastDocumentSize;
     private List<Shader> lastCustomImageShaders = new();
     private List<Shader> lastCustomImageShaders = new();
 
 
     private Dictionary<string, (InputProperty prop, UniformValueType valueType)> uniformInputs = new();
     private Dictionary<string, (InputProperty prop, UniformValueType valueType)> uniformInputs = new();
@@ -75,7 +74,6 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
             shader = shader.WithUpdatedUniforms(uniforms);
             shader = shader.WithUpdatedUniforms(uniforms);
         }
         }
 
 
-        lastDocumentSize = context.DocumentSize;
         paint.Shader = shader;
         paint.Shader = shader;
     }
     }
 
 
@@ -207,15 +205,12 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         }
         }
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
-    {
-        return new RectD(0, 0, lastDocumentSize.X, lastDocumentSize.Y);
-    }
-
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
     {
+        int saved = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
         OnPaint(context, renderOn);
         OnPaint(context, renderOn);
-        return true;
+        renderOn.Canvas.RestoreToCount(saved);
     }
     }
 
 
     public override Node CreateCopy()
     public override Node CreateCopy()

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

@@ -21,7 +21,7 @@ public class EllipseVectorData : ShapeVectorData, IReadOnlyEllipseData
         RectD.FromCenterAndSize(Center, Radius * 2).Inflate(StrokeWidth / 2);
         RectD.FromCenterAndSize(Center, Radius * 2).Inflate(StrokeWidth / 2);
 
 
     public override ShapeCorners TransformationCorners =>
     public override ShapeCorners TransformationCorners =>
-        new ShapeCorners(VisualAABB).WithMatrix(TransformationMatrix);
+        new ShapeCorners(GeometryAABB).WithMatrix(TransformationMatrix);
 
 
 
 
     public EllipseVectorData(VecD center, VecD radius)
     public EllipseVectorData(VecD center, VecD radius)

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

@@ -15,7 +15,7 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
     public override RectD VisualAABB => GeometryAABB.Inflate(StrokeWidth / 2);
     public override RectD VisualAABB => GeometryAABB.Inflate(StrokeWidth / 2);
 
 
     public override ShapeCorners TransformationCorners =>
     public override ShapeCorners TransformationCorners =>
-        new ShapeCorners(VisualAABB).WithMatrix(TransformationMatrix);
+        new ShapeCorners(Path.TightBounds).WithMatrix(TransformationMatrix);
 
 
     public StrokeCap StrokeLineCap { get; set; } = StrokeCap.Round;
     public StrokeCap StrokeLineCap { get; set; } = StrokeCap.Round;
 
 

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

@@ -27,7 +27,7 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
     }
     }
 
 
     public override ShapeCorners TransformationCorners =>
     public override ShapeCorners TransformationCorners =>
-        new ShapeCorners(VisualAABB).WithMatrix(TransformationMatrix);
+        new ShapeCorners(GeometryAABB).WithMatrix(TransformationMatrix);
 
 
 
 
     public RectangleVectorData(VecD center, VecD size)
     public RectangleVectorData(VecD center, VecD size)

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

@@ -34,20 +34,23 @@ public class RasterizeShapeNode : RenderNode
 
 
     public override Node CreateCopy() => new RasterizeShapeNode();
     public override Node CreateCopy() => new RasterizeShapeNode();
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName = "")
     {
     {
         return Data?.Value?.TransformedAABB;
         return Data?.Value?.TransformedAABB;
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    protected override bool ShouldRenderPreview(string elementToRenderName)
+    {
+        return Data.Value != null && Data.Value.IsValid();
+    }
+
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
     {
     {
         var shape = Data.Value;
         var shape = Data.Value;
 
 
         if (shape == null || !shape.IsValid())
         if (shape == null || !shape.IsValid())
-            return false;
+            return;
 
 
         shape.RasterizeTransformed(renderOn.Canvas);
         shape.RasterizeTransformed(renderOn.Canvas);
-
-        return true;
     }
     }
 }
 }

+ 59 - 75
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs

@@ -47,8 +47,6 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
 
     public ChunkyImage? EmbeddedMask { get; set; }
     public ChunkyImage? EmbeddedMask { get; set; }
 
 
-    protected Texture renderedMask;
-
     protected static readonly Paint replacePaint =
     protected static readonly Paint replacePaint =
         new Paint() { BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.Src };
         new Paint() { BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.Src };
 
 
@@ -171,11 +169,40 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         VecD sceneSize = GetSceneSize(context.FrameTime);
         VecD sceneSize = GetSceneSize(context.FrameTime);
         //renderTarget.Canvas.ClipRect(new RectD(scenePos - (sceneSize / 2f), sceneSize));
         //renderTarget.Canvas.ClipRect(new RectD(scenePos - (sceneSize / 2f), sceneSize));
 
 
+        // Custom shader may modify the actual visible region, so we must force rendering full region
+        if (IsConnectedToCustomShaderNode(output))
+        {
+            renderObjectContext.VisibleDocumentRegion = null;
+        }
+
         Render(renderObjectContext);
         Render(renderObjectContext);
 
 
         renderTarget?.Canvas.RestoreToCount(renderSaved);
         renderTarget?.Canvas.RestoreToCount(renderSaved);
     }
     }
 
 
+    private bool IsConnectedToCustomShaderNode(RenderOutputProperty output)
+    {
+        foreach (var conn in output.Connections)
+        {
+            bool isCustomShader = false;
+            conn.Node.TraverseForwards(x =>
+            {
+                if (x is ICustomShaderNode)
+                {
+                    isCustomShader = true;
+                    return false;
+                }
+
+                return true;
+            });
+
+            if (isCustomShader)
+                return true;
+        }
+
+        return false;
+    }
+
     protected SceneObjectRenderContext CreateSceneContext(RenderContext context, DrawingSurface renderTarget,
     protected SceneObjectRenderContext CreateSceneContext(RenderContext context, DrawingSurface renderTarget,
         RenderOutputProperty output)
         RenderOutputProperty output)
     {
     {
@@ -183,9 +210,13 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         RectD localBounds = new RectD(0, 0, sceneSize.X, sceneSize.Y);
         RectD localBounds = new RectD(0, 0, sceneSize.X, sceneSize.Y);
 
 
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
-            context.FrameTime, context.ChunkResolution, context.RenderOutputSize, context.DocumentSize, renderTarget == context.RenderSurface,
+            context.FrameTime, context.ChunkResolution, context.RenderOutputSize, context.DocumentSize,
+            renderTarget == context.RenderSurface,
             context.ProcessingColorSpace, context.DesiredSamplingOptions, context.Opacity);
             context.ProcessingColorSpace, context.DesiredSamplingOptions, context.Opacity);
         renderObjectContext.FullRerender = context.FullRerender;
         renderObjectContext.FullRerender = context.FullRerender;
+        renderObjectContext.AffectedArea = context.AffectedArea;
+        renderObjectContext.VisibleDocumentRegion = context.VisibleDocumentRegion;
+        renderObjectContext.PreviewTextures = context.PreviewTextures;
         return renderObjectContext;
         return renderObjectContext;
     }
     }
 
 
@@ -203,22 +234,12 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
 
 
                 surface.Canvas.RestoreToCount(layer);
                 surface.Canvas.RestoreToCount(layer);
             }
             }
-            else if (EmbeddedMask != null)
+            else
             {
             {
-                if (context.FullRerender)
-                {
-                    EmbeddedMask.DrawMostUpToDateRegionOn(
-                        new RectI(0, 0, EmbeddedMask.LatestSize.X, EmbeddedMask.LatestSize.Y),
-                        ChunkResolution.Full,
-                        surface, VecI.Zero, maskPaint);
-                }
-                else if (renderedMask != null)
-                {
-                    int saved = surface.Canvas.Save();
-                    surface.Canvas.Scale((float)renderResolution.Multiplier());
-                    surface.Canvas.DrawSurface(renderedMask.DrawingSurface, 0, 0, maskPaint);
-                    surface.Canvas.RestoreToCount(saved);
-                }
+                EmbeddedMask?.DrawMostUpToDateRegionOn(
+                    new RectI(0, 0, EmbeddedMask.LatestSize.X, EmbeddedMask.LatestSize.Y),
+                    context.ChunkResolution,
+                    surface, VecI.Zero, maskPaint, drawPaintOnEmpty: true);
             }
             }
         }
         }
     }
     }
@@ -229,48 +250,6 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
             ClipToPreviousMember ? 1 : 0);
             ClipToPreviousMember ? 1 : 0);
     }
     }
 
 
-    public virtual void RenderChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime,
-        ColorSpace processingColorSpace)
-    {
-        RenderChunkyImageChunk(chunkPos, resolution, EmbeddedMask, 55, processingColorSpace, ref renderedMask);
-    }
-
-    protected void RenderChunkyImageChunk(VecI chunkPos, ChunkResolution resolution, ChunkyImage img,
-        int textureId, ColorSpace processingColorSpace,
-        ref Texture? renderSurface)
-    {
-        if (img is null)
-        {
-            return;
-        }
-
-        VecI targetSize = img.LatestSize;
-
-        if (targetSize.X <= 0 || targetSize.Y <= 0)
-        {
-            return;
-        }
-
-        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
-        renderSurface = RequestTexture(textureId, targetSize, processingColorSpace, false);
-
-        int saved = renderSurface.DrawingSurface.Canvas.Save();
-
-        if (!img.DrawMostUpToDateChunkOn(
-                chunkPos,
-                ChunkResolution.Full,
-                renderSurface.DrawingSurface,
-                chunkPos * ChunkResolution.Full.PixelSize(),
-                replacePaint))
-        {
-            var chunkSize = ChunkResolution.Full.PixelSize();
-            renderSurface.DrawingSurface.Canvas.DrawRect(new RectD(chunkPos * chunkSize, new VecD(chunkSize)),
-                clearPaint);
-        }
-
-        renderSurface.DrawingSurface.Canvas.RestoreToCount(saved);
-    }
-
     protected void ApplyRasterClip(DrawingSurface toClip, DrawingSurface clipSource)
     protected void ApplyRasterClip(DrawingSurface toClip, DrawingSurface clipSource)
     {
     {
         if (ClipToPreviousMember && Background.Value != null)
         if (ClipToPreviousMember && Background.Value != null)
@@ -335,40 +314,45 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         }
         }
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementFor = "")
+    protected override bool ShouldRenderPreview(string elementToRenderName)
     {
     {
-        if (elementFor == nameof(EmbeddedMask) && EmbeddedMask != null)
+        if (elementToRenderName == nameof(EmbeddedMask))
         {
         {
-            return new RectD(VecD.Zero, EmbeddedMask.LatestSize);
+            return true;
         }
         }
 
 
-        return null;
+        return EmbeddedMask != null;
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context,
-        string elementToRenderName)
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName)
     {
     {
-        if (elementToRenderName != nameof(EmbeddedMask))
-        {
-            return false;
-        }
+        return EmbeddedMask is null
+            ? null
+            : new RectD(0, 0, EmbeddedMask.LatestSize.X, EmbeddedMask.LatestSize.Y);
+    }
 
 
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context,
+        string elementToRenderName)
+    {
         var img = EmbeddedMask;
         var img = EmbeddedMask;
 
 
         if (img is null)
         if (img is null)
         {
         {
-            return false;
+            return;
         }
         }
 
 
-        renderOn.Canvas.DrawSurface(renderedMask.DrawingSurface, VecI.Zero, maskPreviewPaint);
-
-        return true;
+        int saved = renderOn.Canvas.Save();
+        renderOn.Canvas.Scale((float)context.ChunkResolution.InvertedMultiplier());
+        img.DrawMostUpToDateRegionOn(
+            new RectI(0, 0, img.LatestSize.X, img.LatestSize.Y),
+            context.ChunkResolution,
+            renderOn, VecI.Zero, maskPreviewPaint, drawPaintOnEmpty: true);
+        renderOn.Canvas.RestoreToCount(saved);
     }
     }
 
 
     public override void Dispose()
     public override void Dispose()
     {
     {
         base.Dispose();
         base.Dispose();
-        renderedMask?.Dispose();
         EmbeddedMask?.Dispose();
         EmbeddedMask?.Dispose();
         Output.Value = null;
         Output.Value = null;
         maskPaint.Dispose();
         maskPaint.Dispose();

+ 56 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Text/SliceTextNode.cs

@@ -0,0 +1,56 @@
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Text;
+
+[NodeInfo("SliceText")]
+public class SliceTextNode : Node
+{
+    public OutputProperty<string> SlicedText { get; }
+    
+    public InputProperty<bool> UseLength { get; }
+    
+    public InputProperty<string?> Text { get; }
+    
+    public InputProperty<int> Index { get; }
+    
+    public InputProperty<int> Length { get; }
+    
+    public SliceTextNode()
+    {
+        SlicedText = CreateOutput("SlicedText", "TEXT", string.Empty);
+        Text = CreateInput("Text", "TEXT", string.Empty);
+        UseLength = CreateInput("UseLength", "TEXT_SLICE_USE_LENGTH", true);
+        
+        Index = CreateInput("Index", "INDEX_START_AT", 0)
+            .WithRules(x => x.Min(0));
+        
+        Length = CreateInput("Length", "LENGTH", 1)
+            .WithRules(x => x.Min(0));
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        var text = Text.Value;
+
+        if (text == null)
+        {
+            SlicedText.Value = string.Empty;
+            return;
+        }
+
+        var startIndex = Math.Clamp(Index.Value, 0, text.Length);
+
+        if (!UseLength.Value)
+        {
+            SlicedText.Value = text.Substring(startIndex);
+            return;
+        }
+
+        var length = Math.Clamp(Length.Value, 0, text.Length - startIndex);
+        
+        SlicedText.Value = text.Substring(startIndex, length);
+    }
+
+    public override Node CreateCopy() =>
+        new SliceTextNode();
+}

+ 51 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Text/TextIndexOfNode.cs

@@ -0,0 +1,51 @@
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Text;
+
+[NodeInfo("CharacterPosition")]
+public class TextIndexOfNode : Node
+{
+    public OutputProperty<int> FirstIndex { get; }
+    
+    public OutputProperty<int> LastIndex { get; }
+    
+    public InputProperty<bool> MatchCase { get; }
+    
+    public InputProperty<string?> Text { get; }
+    
+    public InputProperty<string?> SearchText { get; }
+
+    public TextIndexOfNode()
+    {
+        FirstIndex = CreateOutput("FirstIndex", "FIRST_POSITION", -1);
+        LastIndex = CreateOutput("LastIndex", "LAST_POSITION", -1);
+        
+        MatchCase = CreateInput("MatchCase", "MATCH_CASE", false);
+        Text = CreateInput("Text", "TEXT", string.Empty);
+        SearchText = CreateInput("SearchText", "SEARCH_TEXT", string.Empty);
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        var comparisonMode = MatchCase.Value
+            ? StringComparison.InvariantCulture
+            : StringComparison.InvariantCultureIgnoreCase;
+
+        var text = Text.Value;
+        var searchText = SearchText.Value;
+
+        if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(searchText))
+        {
+            FirstIndex.Value = -1;
+            LastIndex.Value = -1;
+            
+            return;
+        }
+
+        FirstIndex.Value = text.IndexOf(searchText, comparisonMode);
+        LastIndex.Value = text.LastIndexOf(searchText, comparisonMode);
+    }
+
+    public override Node CreateCopy() =>
+        new TextIndexOfNode();
+}

+ 39 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Text/TextInfoNode.cs

@@ -0,0 +1,39 @@
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Text;
+
+[NodeInfo("TextInfo")]
+public class TextInfoNode : Node
+{
+    public OutputProperty<int> Length { get; }
+    
+    public OutputProperty<int> LineCount { get; }
+    
+    public InputProperty<string> Text { get; }
+
+    public TextInfoNode()
+    {
+        Length = CreateOutput("Length", "TEXT_LENGTH", 0);
+        LineCount = CreateOutput("LineCount", "TEXT_LINE_COUNT", 0);
+        
+        Text = CreateInput("Text", "TEXT", string.Empty);
+    }
+    
+    protected override void OnExecute(RenderContext context)
+    {
+        var text = Text.Value;
+
+        if (string.IsNullOrEmpty(text))
+        {
+            Length.Value = 0;
+            LineCount.Value = 0;
+            return;
+        }
+        
+        Length.Value = text.Length;
+        LineCount.Value = text.AsSpan().Count('\n');
+    }
+
+    public override Node CreateCopy() =>
+        new TextInfoNode();
+}

+ 0 - 11
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TileNode.cs

@@ -69,15 +69,4 @@ public class TileNode : RenderNode
     {
     {
         return new TileNode();
         return new TileNode();
     }
     }
-
-    public override RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
-    {
-        return null;
-    }
-
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
-    {
-        return false;
-    }
-
 }
 }

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

@@ -90,31 +90,35 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
         Rasterize(workingSurface, paint);
         Rasterize(workingSurface, paint);
     }
     }
 
 
-    public override RectD? GetPreviewBounds(int frame, string elementFor = "")
+    protected override bool ShouldRenderPreview(string elementToRenderName)
     {
     {
-        if (elementFor == nameof(EmbeddedMask))
+        if(RenderableShapeData == null)
         {
         {
-            base.GetPreviewBounds(frame, elementFor);
-        }
-        else
-        {
-            return RenderableShapeData?.TransformedVisualAABB;
+            return false;
         }
         }
 
 
-        return null;
+        VecI tightBoundsSize = (VecI)RenderableShapeData.TransformedVisualAABB.Size;
+
+        VecI translation = new VecI(
+            (int)Math.Max(RenderableShapeData.TransformedAABB.TopLeft.X, 0),
+            (int)Math.Max(RenderableShapeData.TransformedAABB.TopLeft.Y, 0));
+
+        VecI size = tightBoundsSize + translation;
+        return size.X > 0 && size.Y > 0;
     }
     }
 
 
-    public override bool RenderPreview(DrawingSurface renderOn, RenderContext context,
+    public override void RenderPreview(DrawingSurface renderOn, RenderContext context,
         string elementToRenderName)
         string elementToRenderName)
     {
     {
         if (elementToRenderName == nameof(EmbeddedMask))
         if (elementToRenderName == nameof(EmbeddedMask))
         {
         {
-            return base.RenderPreview(renderOn, context, elementToRenderName);
+            base.RenderPreview(renderOn, context, elementToRenderName);
+            return;
         }
         }
 
 
         if (RenderableShapeData == null)
         if (RenderableShapeData == null)
         {
         {
-            return false;
+            return;
         }
         }
 
 
         using var paint = new Paint();
         using var paint = new Paint();
@@ -129,16 +133,20 @@ public class VectorLayerNode : LayerNode, ITransformableObject, IReadOnlyVectorN
 
 
         if (size.X == 0 || size.Y == 0)
         if (size.X == 0 || size.Y == 0)
         {
         {
-            return false;
+            return;
         }
         }
 
 
-
-        int savedCount = renderOn.Canvas.Save();
-        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
         Rasterize(renderOn, paint);
         Rasterize(renderOn, paint);
-        renderOn.Canvas.RestoreToCount(savedCount);
+    }
+
+    public override RectD? GetPreviewBounds(RenderContext ctx, string elementToRenderName)
+    {
+        if (elementToRenderName == nameof(EmbeddedMask))
+        {
+            return base.GetPreviewBounds(ctx, elementToRenderName);
+        }
 
 
-        return true;
+        return GetTightBounds(ctx.FrameTime);
     }
     }
 
 
     public override RectD? GetApproxBounds(KeyFrameTime frameTime)
     public override RectD? GetApproxBounds(KeyFrameTime frameTime)

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

@@ -6,7 +6,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 
 
 [NodeInfo("CustomOutput")]
 [NodeInfo("CustomOutput")]
-public class CustomOutputNode : Node, IRenderInput, IPreviewRenderable
+public class CustomOutputNode : Node, IRenderInput
 {
 {
     public const string OutputNamePropertyName = "OutputName";
     public const string OutputNamePropertyName = "OutputName";
     public const string IsDefaultExportPropertyName = "IsDefaultExport";
     public const string IsDefaultExportPropertyName = "IsDefaultExport";

+ 77 - 0
src/PixiEditor.ChangeableDocument/Changes/Animation/CreateAnimationDataFromFolder_Change.cs

@@ -0,0 +1,77 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
+
+namespace PixiEditor.ChangeableDocument.Changes.Animation;
+
+internal class CreateAnimationDataFromFolder_Change : Change
+{
+    private readonly Guid folderGuid;
+    private Guid[] layerGuids;
+
+    [GenerateMakeChangeAction]
+    public CreateAnimationDataFromFolder_Change(Guid folderGuid)
+    {
+        this.folderGuid = folderGuid;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        if (!target.TryFindMember<FolderNode>(folderGuid, out FolderNode? layer))
+        {
+            return false;
+        }
+
+        var layers = target.ExtractLayers([layer.Id]);
+        if (layers.Count == 0) return false;
+
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
+        out bool ignoreInUndo)
+    {
+        FolderNode folder = target.FindNode(folderGuid) as FolderNode;
+        List<IChangeInfo> infos = new List<IChangeInfo>();
+        layerGuids = target.ExtractLayers([folder.Id]).ToArray();
+
+        foreach (var layer in layerGuids)
+        {
+            var node = target.FindNode(layer);
+            if(node is not LayerNode) continue;
+            foreach (var frame in node.KeyFrames)
+            {
+                if(frame.StartFrame == 0 && frame.Duration == 0) continue;
+                Guid keyFrameId = frame.KeyFrameGuid;
+                target.AnimationData.AddKeyFrame(new RasterKeyFrame(keyFrameId, node.Id, frame.StartFrame, target)
+                {
+                    Duration = frame.Duration,
+                    IsVisible = frame.IsVisible,
+                });
+                infos.Add(new CreateRasterKeyFrame_ChangeInfo(node.Id, frame.StartFrame, keyFrameId, true));
+                infos.Add(new KeyFrameLength_ChangeInfo(keyFrameId, frame.StartFrame, frame.Duration));
+                infos.Add(new KeyFrameVisibility_ChangeInfo(keyFrameId, frame.IsVisible));
+            }
+        }
+
+        ignoreInUndo = false;
+        return infos;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var layer = target.FindNode(folderGuid) as FolderNode;
+        List<IChangeInfo> infos = new List<IChangeInfo>();
+
+        var keyFrame = target.AnimationData.KeyFrames;
+        var ids = keyFrame.Where(x => x.NodeId == layer.Id || layerGuids.Contains(x.NodeId)).Select(x => x.Id).ToList();
+
+        foreach (var id in ids)
+        {
+            target.AnimationData.RemoveKeyFrame(id);
+            infos.Add(new DeleteKeyFrame_ChangeInfo(id));
+        }
+
+        return infos;
+    }
+}

+ 4 - 1
src/PixiEditor.ChangeableDocument/Changes/Animation/CreateAnimationDataFromLayer_Change.cs

@@ -30,9 +30,12 @@ internal class CreateAnimationDataFromLayer_Change : Change
             Guid keyFrameId = frame.KeyFrameGuid;
             Guid keyFrameId = frame.KeyFrameGuid;
             target.AnimationData.AddKeyFrame(new RasterKeyFrame(keyFrameId, layer.Id, frame.StartFrame, target)
             target.AnimationData.AddKeyFrame(new RasterKeyFrame(keyFrameId, layer.Id, frame.StartFrame, target)
             {
             {
-                Duration = frame.Duration
+                Duration = frame.Duration,
+                IsVisible = frame.IsVisible,
             });
             });
             infos.Add(new CreateRasterKeyFrame_ChangeInfo(layer.Id, frame.StartFrame, keyFrameId, true));
             infos.Add(new CreateRasterKeyFrame_ChangeInfo(layer.Id, frame.StartFrame, keyFrameId, true));
+            infos.Add(new KeyFrameLength_ChangeInfo(keyFrameId, frame.StartFrame, frame.Duration));
+            infos.Add(new KeyFrameVisibility_ChangeInfo(keyFrameId, frame.IsVisible));
         }
         }
 
 
         ignoreInUndo = false;
         ignoreInUndo = false;

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

@@ -2,10 +2,12 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
@@ -52,26 +54,34 @@ public static class FloodFillHelper
 
 
         int chunkSize = ChunkResolution.Full.PixelSize();
         int chunkSize = ChunkResolution.Full.PixelSize();
 
 
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
+
         FloodFillChunkCache cache = CreateCache(membersToFloodFill, document, frame);
         FloodFillChunkCache cache = CreateCache(membersToFloodFill, document, frame);
 
 
         VecI initChunkPos = OperationHelper.GetChunkPos(startingPos, chunkSize);
         VecI initChunkPos = OperationHelper.GetChunkPos(startingPos, chunkSize);
         VecI imageSizeInChunks = (VecI)(document.Size / (double)chunkSize).Ceiling();
         VecI imageSizeInChunks = (VecI)(document.Size / (double)chunkSize).Ceiling();
         VecI initPosOnChunk = startingPos - initChunkPos * chunkSize;
         VecI initPosOnChunk = startingPos - initChunkPos * chunkSize;
         var chunkAtPos = cache.GetChunk(initChunkPos);
         var chunkAtPos = cache.GetChunk(initChunkPos);
-        Color colorToReplace = chunkAtPos.Match(
-            (Chunk chunk) => chunk.Surface.GetRawPixel(initPosOnChunk),
+        ColorF colorToReplace = chunkAtPos.Match(
+            (Chunk chunk) => chunk.Surface.GetRawPixelPrecise(initPosOnChunk),
             static (EmptyChunk _) => Colors.Transparent
             static (EmptyChunk _) => Colors.Transparent
         );
         );
 
 
         ulong uLongColor = drawingColor.ToULong();
         ulong uLongColor = drawingColor.ToULong();
-        Color colorSpaceCorrectedColor = drawingColor;
+        ColorF colorSpaceCorrectedColor = drawingColor;
         if (!document.ProcessingColorSpace.IsSrgb)
         if (!document.ProcessingColorSpace.IsSrgb)
         {
         {
-            var srgbTransform = ColorSpace.CreateSrgb().GetTransformFunction();
+            // Mixing using actual surfaces is more accurate than using ColorTransformFn
+            // mismatch between actual surface color and transformed color here can lead to infinite loops
+            using Surface srgbSurface = Surface.ForProcessing(new VecI(1), ColorSpace.CreateSrgb());
+            using Paint srgbPaint = new Paint(){ Color = drawingColor };
+            srgbSurface.DrawingSurface.Canvas.DrawPixel(0, 0, srgbPaint);
+            using var processingSurface = Surface.ForProcessing(VecI.One, document.ProcessingColorSpace);
+            processingSurface.DrawingSurface.Canvas.DrawSurface(srgbSurface.DrawingSurface, 0, 0);
+            var fixedColor = processingSurface.GetRawPixelPrecise(VecI.Zero);
 
 
-            var fixedColor = drawingColor.TransformColor(srgbTransform);
             uLongColor = fixedColor.ToULong();
             uLongColor = fixedColor.ToULong();
-            colorSpaceCorrectedColor = (Color)fixedColor;
+            colorSpaceCorrectedColor = fixedColor;
         }
         }
 
 
         if ((colorSpaceCorrectedColor.A == 0) || colorToReplace == colorSpaceCorrectedColor)
         if ((colorSpaceCorrectedColor.A == 0) || colorToReplace == colorSpaceCorrectedColor)
@@ -192,15 +202,16 @@ public static class FloodFillHelper
         VecI chunkPos,
         VecI chunkPos,
         int chunkSize,
         int chunkSize,
         ulong colorBits,
         ulong colorBits,
-        Color color,
+        ColorF color,
         VecI pos,
         VecI pos,
         ColorBounds bounds,
         ColorBounds bounds,
         bool checkFirstPixel)
         bool checkFirstPixel)
     {
     {
+        var rawPixelRef = referenceChunk.Surface.GetRawPixelPrecise(pos);
         // color should be a fixed color
         // color should be a fixed color
-        if (referenceChunk.Surface.GetRawPixel(pos) == color || drawingChunk.Surface.GetRawPixel(pos) == color)
+        if ((Color)rawPixelRef == (Color)color || (Color)drawingChunk.Surface.GetRawPixelPrecise(pos) == (Color)color)
             return null;
             return null;
-        if (checkFirstPixel && !bounds.IsWithinBounds(referenceChunk.Surface.GetRawPixel(pos)))
+        if (checkFirstPixel && !bounds.IsWithinBounds(rawPixelRef))
             return null;
             return null;
         
         
         if(!SelectionIntersectsChunk(selection, chunkPos, chunkSize))
         if(!SelectionIntersectsChunk(selection, chunkPos, chunkSize))
@@ -209,10 +220,12 @@ public static class FloodFillHelper
         byte[] pixelStates = new byte[chunkSize * chunkSize];
         byte[] pixelStates = new byte[chunkSize * chunkSize];
         DrawSelection(pixelStates, selection, globalSelectionBounds, chunkPos, chunkSize);
         DrawSelection(pixelStates, selection, globalSelectionBounds, chunkPos, chunkSize);
 
 
-        using var refPixmap = referenceChunk.Surface.DrawingSurface.PeekPixels();
+        using var refPixmap = referenceChunk.Surface.PeekPixels();
         Half* refArray = (Half*)refPixmap.GetPixels();
         Half* refArray = (Half*)refPixmap.GetPixels();
 
 
-        using var drawPixmap = drawingChunk.Surface.DrawingSurface.PeekPixels();
+        Surface cpuSurface = Surface.ForProcessing(new VecI(chunkSize), referenceChunk.Surface.ColorSpace);
+        cpuSurface.DrawingSurface.Canvas.DrawSurface(drawingChunk.Surface.DrawingSurface, 0, 0);
+        using var drawPixmap = cpuSurface.PeekPixels();
         Half* drawArray = (Half*)drawPixmap.GetPixels();
         Half* drawArray = (Half*)drawPixmap.GetPixels();
 
 
         Stack<VecI> toVisit = new();
         Stack<VecI> toVisit = new();
@@ -240,6 +253,10 @@ public static class FloodFillHelper
                 toVisit.Push(new(curPos.X, curPos.Y + 1));
                 toVisit.Push(new(curPos.X, curPos.Y + 1));
         }
         }
 
 
+        using Paint replacePaint = new Paint();
+        replacePaint.BlendMode = BlendMode.Src;
+        drawingChunk.Surface.DrawingSurface.Canvas.DrawSurface(cpuSurface.DrawingSurface, 0, 0, replacePaint);
+
         return pixelStates;
         return pixelStates;
     }
     }
 
 

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

@@ -57,7 +57,7 @@ internal class FloodFill_Change : Change
 
 
         foreach (var (chunkPos, chunk) in floodFilledChunks)
         foreach (var (chunkPos, chunk) in floodFilledChunks)
         {
         {
-            image.EnqueueDrawImage(chunkPos * ChunkyImage.FullChunkSize, chunk.Surface, null, false);
+            image.EnqueueDrawTexture(chunkPos * ChunkyImage.FullChunkSize, chunk.Surface, null, false);
         }
         }
         var affArea = image.FindAffectedArea();
         var affArea = image.FindAffectedArea();
         chunkStorage = new CommittedChunkStorage(image, affArea.Chunks);
         chunkStorage = new CommittedChunkStorage(image, affArea.Chunks);

+ 5 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelected_UpdateableChange.cs

@@ -28,6 +28,7 @@ internal class TransformSelected_UpdateableChange : InterruptableUpdateableChang
     private VecD tightBoundsSize;
     private VecD tightBoundsSize;
     private RectD cornersToSelectionOffset;
     private RectD cornersToSelectionOffset;
     private VecD originalCornersSize;
     private VecD originalCornersSize;
+    private bool bilinearFiltering;
 
 
     private bool isTransformingSelection;
     private bool isTransformingSelection;
     private bool hasEnqueudImages = false;
     private bool hasEnqueudImages = false;
@@ -42,11 +43,13 @@ internal class TransformSelected_UpdateableChange : InterruptableUpdateableChang
     public TransformSelected_UpdateableChange(
     public TransformSelected_UpdateableChange(
         ShapeCorners masterCorners,
         ShapeCorners masterCorners,
         bool keepOriginal,
         bool keepOriginal,
+        bool bilinearFiltering,
         Dictionary<Guid, ShapeCorners> memberCorners,
         Dictionary<Guid, ShapeCorners> memberCorners,
         bool transformMask,
         bool transformMask,
         int frame)
         int frame)
     {
     {
         memberData = new();
         memberData = new();
+        this.bilinearFiltering = bilinearFiltering;
         foreach (var corners in memberCorners)
         foreach (var corners in memberCorners)
         {
         {
             memberData.Add(new MemberTransformationData(corners.Key) { MemberCorners = corners.Value });
             memberData.Add(new MemberTransformationData(corners.Key) { MemberCorners = corners.Value });
@@ -91,7 +94,7 @@ internal class TransformSelected_UpdateableChange : InterruptableUpdateableChang
 
 
         if (memberData.Count == 1 && firstLayer is VectorLayerNode vectorLayer)
         if (memberData.Count == 1 && firstLayer is VectorLayerNode vectorLayer)
         {
         {
-            tightBounds = vectorLayer.EmbeddedShapeData?.VisualAABB ?? default;
+            tightBounds = vectorLayer.EmbeddedShapeData?.GeometryAABB ?? default;
         }
         }
 
 
         for (var i = 1; i < memberData.Count; i++)
         for (var i = 1; i < memberData.Count; i++)
@@ -417,7 +420,7 @@ internal class TransformSelected_UpdateableChange : InterruptableUpdateableChang
             finalPaint = LockedAlphaPaint;
             finalPaint = LockedAlphaPaint;
         }
         }
 
 
-        memberImage.EnqueueDrawImage(data.LocalMatrix, data.Image, finalPaint, false);
+        memberImage.EnqueueDrawImage(data.LocalMatrix, data.Image, bilinearFiltering ? SamplingOptions.Bilinear : SamplingOptions.Default, finalPaint, false);
         hasEnqueudImages = true;
         hasEnqueudImages = true;
 
 
         var affectedArea = memberImage.FindAffectedArea();
         var affectedArea = memberImage.FindAffectedArea();

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs

@@ -52,9 +52,9 @@ internal class ClipCanvas_Change : ResizeBasedChangeBase
         {
         {
             if (member is ImageLayerNode layer)
             if (member is ImageLayerNode layer)
             {
             {
-                layer.ForEveryFrame(img =>
+                layer.ForEveryFrame((img, id) =>
                 {
                 {
-                    Resize(img, layer.Id, size, -(VecI)newBounds.Pos, deletedChunks);
+                    Resize(img, id, size, -(VecI)newBounds.Pos, deletedChunks);
                 });
                 });
             }
             }
             else if (member is ITransformableObject transformableObject)
             else if (member is ITransformableObject transformableObject)

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/Crop_Change.cs

@@ -37,9 +37,9 @@ internal class Crop_Change : ResizeBasedChangeBase
         {
         {
             if (member is ImageLayerNode layer)
             if (member is ImageLayerNode layer)
             {
             {
-                layer.ForEveryFrame(frame =>
+                layer.ForEveryFrame((frame, id) =>
                 {
                 {
-                    Resize(frame, layer.Id, rect.Size, rect.Pos * -1, deletedChunks);
+                    Resize(frame, id, rect.Size, rect.Pos * -1, deletedChunks);
                 });
                 });
             }
             }
             if (member.EmbeddedMask is null)
             if (member.EmbeddedMask is null)

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs

@@ -52,10 +52,10 @@ internal abstract class ResizeBasedChangeBase : Change
         {
         {
             if (member is ImageLayerNode layer)
             if (member is ImageLayerNode layer)
             {
             {
-                layer.ForEveryFrame(img =>
+                layer.ForEveryFrame((img, id) =>
                 {
                 {
                     img.EnqueueResize(_originalSize);
                     img.EnqueueResize(_originalSize);
-                    foreach (var stored in deletedChunks[layer.Id])
+                    foreach (var stored in deletedChunks[id])
                         stored.ApplyChunksToImage(img);
                         stored.ApplyChunksToImage(img);
                     img.CommitChanges();
                     img.CommitChanges();
                 });
                 });

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs

@@ -49,9 +49,9 @@ internal class ResizeCanvas_Change : ResizeBasedChangeBase
         {
         {
             if (member is ImageLayerNode layer)
             if (member is ImageLayerNode layer)
             {
             {
-                layer.ForEveryFrame(img =>
+                layer.ForEveryFrame((img, id) =>
                 {
                 {
-                    Resize(img, layer.Id, newSize, offset, deletedChunks);
+                    Resize(img, id, newSize, offset, deletedChunks);
                 });
                 });
             }
             }
 
 

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs

@@ -91,11 +91,11 @@ internal class ResizeImage_Change : Change
         {
         {
             if (member is ImageLayerNode layer)
             if (member is ImageLayerNode layer)
             {
             {
-                layer.ForEveryFrame(img =>
+                layer.ForEveryFrame((img, id) =>
                 {
                 {
                     ScaleChunkyImage(img);
                     ScaleChunkyImage(img);
                     var affected = img.FindAffectedArea();
                     var affected = img.FindAffectedArea();
-                    savedChunks[layer.Id] = new CommittedChunkStorage(img, affected.Chunks);
+                    savedChunks[id] = new CommittedChunkStorage(img, affected.Chunks);
                     img.CommitChanges();
                     img.CommitChanges();
                 });
                 });
             }
             }
@@ -127,11 +127,11 @@ internal class ResizeImage_Change : Change
         {
         {
             if (member is ImageLayerNode layer)
             if (member is ImageLayerNode layer)
             {
             {
-                layer.ForEveryFrame(layerImage =>
+                layer.ForEveryFrame((layerImage, id) =>
                 {
                 {
                     layerImage.EnqueueResize(originalSize);
                     layerImage.EnqueueResize(originalSize);
                     layerImage.EnqueueClear();
                     layerImage.EnqueueClear();
-                    savedChunks[layer.Id].ApplyChunksToImage(layerImage);
+                    savedChunks[id].ApplyChunksToImage(layerImage);
                     layerImage.CommitChanges();
                     layerImage.CommitChanges();
                 });
                 });
             }
             }

+ 6 - 4
src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand/MagicWandHelper.cs

@@ -1,5 +1,6 @@
 using System.Collections;
 using System.Collections;
 using ChunkyImageLib.Operations;
 using ChunkyImageLib.Operations;
+using Drawie.Backend.Core.Bridge;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
 using PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
@@ -125,8 +126,8 @@ internal class MagicWandHelper
         VecI initPosOnChunk = startingPos - initChunkPos * chunkSize;
         VecI initPosOnChunk = startingPos - initChunkPos * chunkSize;
 
 
 
 
-        Color colorToReplace = cache.GetChunk(initChunkPos).Match(
-            (Chunk chunk) => chunk.Surface.GetRawPixel(initPosOnChunk),
+        ColorF colorToReplace = cache.GetChunk(initChunkPos).Match(
+            (Chunk chunk) => chunk.Surface.GetRawPixelPrecise(initPosOnChunk),
             static (EmptyChunk _) => Colors.Transparent
             static (EmptyChunk _) => Colors.Transparent
         );
         );
 
 
@@ -257,14 +258,15 @@ internal class MagicWandHelper
         VecI pos,
         VecI pos,
         ColorBounds bounds, Lines lines)
         ColorBounds bounds, Lines lines)
     {
     {
-        if (!bounds.IsWithinBounds(referenceChunk.Surface.GetRawPixel(pos)))
+        using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
+        if (!bounds.IsWithinBounds(referenceChunk.Surface.GetRawPixelPrecise(pos)))
         {
         {
             return null;
             return null;
         }
         }
 
 
         bool[] pixelVisitedStates = new bool[chunkSize * chunkSize];
         bool[] pixelVisitedStates = new bool[chunkSize * chunkSize];
 
 
-        using var refPixmap = referenceChunk.Surface.DrawingSurface.PeekPixels();
+        using var refPixmap = referenceChunk.Surface.PeekPixels();
         Half* refArray = (Half*)refPixmap.GetPixels();
         Half* refArray = (Half*)refPixmap.GetPixels();
 
 
         Stack<VecI> toVisit = new();
         Stack<VecI> toVisit = new();

+ 62 - 14
src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateFolder_Change.cs

@@ -1,6 +1,7 @@
 using System.Collections.Immutable;
 using System.Collections.Immutable;
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
@@ -17,6 +18,8 @@ internal class DuplicateFolder_Change : Change
     private Guid[] contentDuplicateGuids;
     private Guid[] contentDuplicateGuids;
 
 
     private Guid[]? childGuidsToUse;
     private Guid[]? childGuidsToUse;
+    private Dictionary<Guid, List<Guid>> keyFramesMap = new();
+    private Dictionary<Guid, Guid> nodeMap = new();
 
 
     private ConnectionsData? connectionsData;
     private ConnectionsData? connectionsData;
     private Dictionary<Guid, ConnectionsData> contentConnectionsData = new();
     private Dictionary<Guid, ConnectionsData> contentConnectionsData = new();
@@ -64,14 +67,15 @@ internal class DuplicateFolder_Change : Change
         List<IChangeInfo> operations = new();
         List<IChangeInfo> operations = new();
 
 
         target.NodeGraph.AddNode(clone);
         target.NodeGraph.AddNode(clone);
-        
+
         var previousConnection = targetInput.Connection;
         var previousConnection = targetInput.Connection;
 
 
         operations.Add(CreateNode_ChangeInfo.CreateFromNode(clone));
         operations.Add(CreateNode_ChangeInfo.CreateFromNode(clone));
         operations.AddRange(NodeOperations.AppendMember(targetInput, clone.Output, clone.Background, clone.Id));
         operations.AddRange(NodeOperations.AppendMember(targetInput, clone.Output, clone.Background, clone.Id));
-        operations.AddRange(NodeOperations.AdjustPositionsAfterAppend(clone, targetInput.Node, previousConnection?.Node as Node, out originalPositions));
+        operations.AddRange(NodeOperations.AdjustPositionsAfterAppend(clone, targetInput.Node,
+            previousConnection?.Node as Node, out originalPositions));
 
 
-        DuplicateContent(target, clone, existingLayer, operations);
+        DuplicateContent(target, clone, existingLayer, operations, firstApply);
 
 
         ignoreInUndo = false;
         ignoreInUndo = false;
 
 
@@ -116,29 +120,70 @@ internal class DuplicateFolder_Change : Change
     }
     }
 
 
     private void DuplicateContent(Document target, FolderNode clone, FolderNode existingLayer,
     private void DuplicateContent(Document target, FolderNode clone, FolderNode existingLayer,
-        List<IChangeInfo> operations)
+        List<IChangeInfo> operations, bool firstApply)
     {
     {
-        Dictionary<Guid, Guid> nodeMap = new Dictionary<Guid, Guid>();
+        if (firstApply)
+        {
+            nodeMap = new Dictionary<Guid, Guid>();
+            nodeMap[existingLayer.Id] = clone.Id;
+        }
 
 
-        nodeMap[existingLayer.Id] = clone.Id;
         int counter = 0;
         int counter = 0;
         List<Guid> contentGuidList = new();
         List<Guid> contentGuidList = new();
 
 
+        if (firstApply)
+        {
+            keyFramesMap = new Dictionary<Guid, List<Guid>>();
+        }
+
+        int childCounter = 0;
+
         existingLayer.Content.Connection?.Node.TraverseBackwards(x =>
         existingLayer.Content.Connection?.Node.TraverseBackwards(x =>
         {
         {
             if (x is not Node targetNode)
             if (x is not Node targetNode)
                 return false;
                 return false;
 
 
             Node? node = targetNode.Clone();
             Node? node = targetNode.Clone();
-            
-            if(node is not FolderNode && childGuidsToUse is not null && counter < childGuidsToUse.Length)
+
+            if (contentDuplicateGuids != null && contentDuplicateGuids.Length > 0)
+            {
+                node.Id = contentDuplicateGuids[childCounter];
+                childCounter++;
+            }
+            else
             {
             {
-                node.Id = childGuidsToUse[counter];
-                counter++;
+                if (node is not FolderNode && childGuidsToUse is not null && counter < childGuidsToUse.Length)
+                {
+                    node.Id = childGuidsToUse[counter];
+                    counter++;
+                }
+            }
+
+            if (firstApply)
+            {
+                keyFramesMap[node.Id] = new List<Guid>();
+                keyFramesMap[node.Id].AddRange(x.KeyFrames.Select(kf => kf.KeyFrameGuid));
+            }
+            else
+            {
+                if (keyFramesMap.TryGetValue(node.Id, out List<Guid>? keyFrameGuids))
+                {
+                    for (int i = 0; i < x.KeyFrames.Count; i++)
+                    {
+                        if (i < keyFrameGuids.Count)
+                        {
+                            var kf = x.KeyFrames[i] as KeyFrameData;
+                            kf.KeyFrameGuid = keyFrameGuids[i];
+                        }
+                    }
+                }
+            }
+
+            if (firstApply)
+            {
+                nodeMap[x.Id] = node.Id;
+                contentGuidList.Add(node.Id);
             }
             }
-            
-            nodeMap[x.Id] = node.Id;
-            contentGuidList.Add(node.Id);
 
 
             target.NodeGraph.AddNode(node);
             target.NodeGraph.AddNode(node);
 
 
@@ -154,6 +199,9 @@ internal class DuplicateFolder_Change : Change
                 target.FindNodeOrThrow<Node>(targetNodeId), target.NodeGraph));
                 target.FindNodeOrThrow<Node>(targetNodeId), target.NodeGraph));
         }
         }
 
 
-        contentDuplicateGuids = contentGuidList.ToArray();
+        if (firstApply)
+        {
+            contentDuplicateGuids = contentGuidList.ToArray();
+        }
     }
     }
 }
 }

+ 2 - 1
src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs

@@ -1,4 +1,5 @@
 using System.Diagnostics;
 using System.Diagnostics;
+using Drawie.Backend.Core.Bridge;
 using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
@@ -430,7 +431,7 @@ public class DocumentChangeTracker : IDisposable
         if (running)
         if (running)
             throw new InvalidOperationException("Already currently processing");
             throw new InvalidOperationException("Already currently processing");
         running = true;
         running = true;
-        var result = await Task.Run(() => ProcessActionList(actions)).ConfigureAwait(true);
+        var result = await DrawingBackendApi.Current.RenderingDispatcher.InvokeAsync(() => ProcessActionList(actions));
         running = false;
         running = false;
         return result;
         return result;
     }
     }

+ 0 - 17
src/PixiEditor.ChangeableDocument/Helpers/PreviewUtils.cs

@@ -1,17 +0,0 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
-using Drawie.Numerics;
-
-namespace PixiEditor.ChangeableDocument.Helpers;
-
-public static class PreviewUtils
-{
-    public static RectD? FindPreviewBounds(IOutputProperty? connectionProperty, int frame, string elementToRenderName)
-    {
-        if (connectionProperty is { Node: IPreviewRenderable previousPreview })
-        {
-            return previousPreview.GetPreviewBounds(frame, elementToRenderName);
-        }
-
-        return null;
-    }
-}

+ 1 - 118
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -17,11 +17,9 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 
 
 namespace PixiEditor.ChangeableDocument.Rendering;
 namespace PixiEditor.ChangeableDocument.Rendering;
 
 
-public class DocumentRenderer : IPreviewRenderable, IDisposable
+public class DocumentRenderer : IDisposable
 {
 {
-    private Queue<RenderRequest> renderRequests = new();
     private Texture renderTexture;
     private Texture renderTexture;
-    private int lastExecutedGraphFrame = -1;
 
 
     public DocumentRenderer(IReadOnlyDocument document)
     public DocumentRenderer(IReadOnlyDocument document)
     {
     {
@@ -31,7 +29,6 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
     private IReadOnlyDocument Document { get; }
     private IReadOnlyDocument Document { get; }
     public bool IsBusy { get; private set; }
     public bool IsBusy { get; private set; }
 
 
-    private bool isExecuting = false;
 
 
     public void UpdateChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime)
     public void UpdateChunk(VecI chunkPos, ChunkResolution resolution, KeyFrameTime frameTime)
     {
     {
@@ -126,20 +123,6 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         IsBusy = false;
         IsBusy = false;
     }
     }
 
 
-    public async Task<bool> RenderNodePreview(IPreviewRenderable previewRenderable, DrawingSurface renderOn,
-        RenderContext context,
-        string elementToRenderName)
-    {
-        if (previewRenderable is Node { IsDisposed: true }) return false;
-        TaskCompletionSource<bool> tcs = new();
-        RenderRequest request = new(tcs, context, renderOn, previewRenderable, elementToRenderName);
-
-        renderRequests.Enqueue(request);
-        ExecuteRenderRequests(context.FrameTime);
-
-        return await tcs.Task;
-    }
-
     public static IReadOnlyNodeGraph ConstructMembersOnlyGraph(IReadOnlyNodeGraph fullGraph)
     public static IReadOnlyNodeGraph ConstructMembersOnlyGraph(IReadOnlyNodeGraph fullGraph)
     {
     {
         return ConstructMembersOnlyGraph(null, fullGraph);
         return ConstructMembersOnlyGraph(null, fullGraph);
@@ -193,27 +176,6 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         return membersOnlyGraph;
         return membersOnlyGraph;
     }
     }
 
 
-    RectD? IPreviewRenderable.GetPreviewBounds(int frame, string elementNameToRender = "") =>
-        new(0, 0, Document.Size.X, Document.Size.Y);
-
-    bool IPreviewRenderable.RenderPreview(DrawingSurface renderOn, RenderContext context,
-        string elementToRenderName)
-    {
-        IsBusy = true;
-
-        renderOn.Canvas.Clear();
-        int savedCount = renderOn.Canvas.Save();
-        renderOn.Canvas.Scale((float)context.ChunkResolution.Multiplier());
-        context.RenderSurface = renderOn;
-        Document.NodeGraph.Execute(context);
-        lastExecutedGraphFrame = context.FrameTime.Frame;
-        renderOn.Canvas.RestoreToCount(savedCount);
-
-        IsBusy = false;
-
-        return true;
-    }
-
     public void RenderDocument(DrawingSurface toRenderOn, KeyFrameTime frameTime, VecI renderSize,
     public void RenderDocument(DrawingSurface toRenderOn, KeyFrameTime frameTime, VecI renderSize,
         string? customOutput = null)
         string? customOutput = null)
     {
     {
@@ -270,52 +232,9 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         renderTexture.DrawingSurface.Canvas.Restore();
         renderTexture.DrawingSurface.Canvas.Restore();
         toRenderOn.Canvas.Restore();
         toRenderOn.Canvas.Restore();
 
 
-        lastExecutedGraphFrame = frameTime.Frame;
-
         IsBusy = false;
         IsBusy = false;
     }
     }
 
 
-    private void ExecuteRenderRequests(KeyFrameTime frameTime)
-    {
-        if (isExecuting) return;
-
-        isExecuting = true;
-        using var ctx = DrawingBackendApi.Current?.RenderingDispatcher.EnsureContext();
-
-        while (renderRequests.Count > 0)
-        {
-            RenderRequest request = renderRequests.Dequeue();
-
-            if (frameTime.Frame != lastExecutedGraphFrame && request.PreviewRenderable != this)
-            {
-                using Texture executeSurface = Texture.ForDisplay(new VecI(1));
-                RenderDocument(executeSurface.DrawingSurface, frameTime, VecI.One);
-            }
-
-            try
-            {
-                bool result = true;
-                if (request.PreviewRenderable != null)
-                {
-                    result = request.PreviewRenderable.RenderPreview(request.RenderOn, request.Context,
-                        request.ElementToRenderName);
-                }
-                else if (request.NodeGraph != null)
-                {
-                    request.NodeGraph.Execute(request.Context);
-                }
-
-                request.TaskCompletionSource.SetResult(result);
-            }
-            catch (Exception e)
-            {
-                request.TaskCompletionSource.SetException(e);
-            }
-        }
-
-        isExecuting = false;
-    }
-
     private static IInputProperty GetTargetInput(IInputProperty? input,
     private static IInputProperty GetTargetInput(IInputProperty? input,
         IReadOnlyNodeGraph sourceGraph,
         IReadOnlyNodeGraph sourceGraph,
         NodeGraph membersOnlyGraph,
         NodeGraph membersOnlyGraph,
@@ -383,41 +302,5 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
     {
     {
         renderTexture?.Dispose();
         renderTexture?.Dispose();
         renderTexture = null;
         renderTexture = null;
-
-        foreach (var request in renderRequests)
-        {
-            if (request.TaskCompletionSource == null) continue;
-
-            request.TaskCompletionSource.TrySetCanceled();
-        }
-    }
-}
-
-public struct RenderRequest
-{
-    public RenderContext Context { get; set; }
-    public DrawingSurface RenderOn { get; set; }
-    public IReadOnlyNodeGraph? NodeGraph { get; set; } // TODO: Implement async rendering for stuff other than previews
-    public IPreviewRenderable? PreviewRenderable { get; set; }
-    public string ElementToRenderName { get; set; }
-    public TaskCompletionSource<bool> TaskCompletionSource { get; set; }
-
-    public RenderRequest(TaskCompletionSource<bool> completionSource, RenderContext context, DrawingSurface renderOn,
-        IReadOnlyNodeGraph nodeGraph)
-    {
-        TaskCompletionSource = completionSource;
-        Context = context;
-        RenderOn = renderOn;
-        NodeGraph = nodeGraph;
-    }
-
-    public RenderRequest(TaskCompletionSource<bool> completionSource, RenderContext context, DrawingSurface renderOn,
-        IPreviewRenderable previewRenderable, string elementToRenderName)
-    {
-        TaskCompletionSource = completionSource;
-        Context = context;
-        RenderOn = renderOn;
-        PreviewRenderable = previewRenderable;
-        ElementToRenderName = elementToRenderName;
     }
     }
 }
 }

+ 43 - 0
src/PixiEditor.ChangeableDocument/Rendering/PreviewRenderRequest.cs

@@ -0,0 +1,43 @@
+using Drawie.Backend.Core;
+
+namespace PixiEditor.ChangeableDocument.Rendering;
+
+public record struct PreviewRenderRequest
+{
+    public Texture? Texture
+    {
+        get
+        {
+            if (!accessedTexture)
+            {
+                texture = textureCreateFunc(true);
+                accessedTexture = true;
+            }
+
+            return texture;
+        }
+    }
+    public string? ElementToRender { get; set; }
+    public Action TextureUpdatedAction { get; set; }
+
+    private Func<bool, Texture?> textureCreateFunc;
+    private Texture? texture;
+    private bool accessedTexture = false;
+
+    public PreviewRenderRequest(Func<bool, Texture?> textureCreateFunc, Action textureUpdatedAction, string? elementToRender = null)
+    {
+        this.textureCreateFunc = textureCreateFunc;
+        TextureUpdatedAction = textureUpdatedAction;
+        ElementToRender = elementToRender;
+    }
+
+    public void InvokeTextureUpdated()
+    {
+        TextureUpdatedAction?.Invoke();
+    }
+
+    public Texture? GetTextureCached()
+    {
+        return textureCreateFunc(false);
+    }
+}

+ 48 - 0
src/PixiEditor.ChangeableDocument/Rendering/PreviewUtility.cs

@@ -0,0 +1,48 @@
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Rendering;
+
+public static class PreviewUtility
+{
+    public static ChunkResolution CalculateResolution(VecD size, VecD textureSize)
+    {
+        VecD densityVec = size.Divide(textureSize);
+        double density = Math.Min(densityVec.X, densityVec.Y);
+        return density switch
+        {
+            > 8.01 => ChunkResolution.Eighth,
+            > 4.01 => ChunkResolution.Quarter,
+            > 2.01 => ChunkResolution.Half,
+            _ => ChunkResolution.Full
+        };
+    }
+
+    public static VecD CalculateUniformScaling(VecD originalSize, VecD targetSize)
+    {
+        if (originalSize.X == 0 || originalSize.Y == 0)
+            return new VecD(1);
+
+        VecD scale = targetSize.Divide(originalSize);
+        double uniformScale = Math.Min(scale.X, scale.Y);
+        return new VecD(uniformScale, uniformScale);
+    }
+
+    public static VecD CalculateCenteringOffset(VecD originalSize, VecD targetSize, VecD scaling)
+    {
+        if (originalSize.X == 0 || originalSize.Y == 0)
+            return VecD.Zero;
+
+        VecD scaledOriginal = originalSize.Multiply(scaling);
+        return (targetSize - scaledOriginal) / 2;
+    }
+
+    public static RenderContext CreatePreviewContext(RenderContext ctx, VecD scaling, VecD renderSize, VecD textureSize)
+    {
+        var clone = ctx.Clone();
+        clone.ChunkResolution = CalculateResolution(renderSize, textureSize);
+        clone.DesiredSamplingOptions = scaling.X > 1 ? SamplingOptions.Default : SamplingOptions.Bilinear;
+
+        return clone;
+    }
+}

+ 18 - 2
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Animations;
+using Drawie.Backend.Core;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
 using Drawie.Numerics;
@@ -14,6 +15,7 @@ public class RenderContext
 
 
     public KeyFrameTime FrameTime { get; }
     public KeyFrameTime FrameTime { get; }
     public ChunkResolution ChunkResolution { get; set; }
     public ChunkResolution ChunkResolution { get; set; }
+    public RectI? VisibleDocumentRegion { get; set; } = null;
     public SamplingOptions DesiredSamplingOptions { get; set; } = SamplingOptions.Default;
     public SamplingOptions DesiredSamplingOptions { get; set; } = SamplingOptions.Default;
     public VecI RenderOutputSize { get; set; }
     public VecI RenderOutputSize { get; set; }
 
 
@@ -24,6 +26,8 @@ public class RenderContext
     public EditorData EditorData { get; set; }
     public EditorData EditorData { get; set; }
     public ColorSpace ProcessingColorSpace { get; set; }
     public ColorSpace ProcessingColorSpace { get; set; }
     public string? TargetOutput { get; set; }
     public string? TargetOutput { get; set; }
+    public AffectedArea AffectedArea { get; set; }
+    public Dictionary<Guid, List<PreviewRenderRequest>>? PreviewTextures { get; set; }
 
 
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
@@ -39,6 +43,15 @@ public class RenderContext
         DesiredSamplingOptions = desiredSampling;
         DesiredSamplingOptions = desiredSampling;
     }
     }
 
 
+    public List<PreviewRenderRequest>? GetPreviewTexturesForNode(Guid id)
+    {
+        if (PreviewTextures is null)
+            return null;
+        PreviewTextures.TryGetValue(id, out List<PreviewRenderRequest> requests);
+        PreviewTextures.Remove(id);
+        return requests;
+    }
+
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
     {
     {
         return blendMode switch
         return blendMode switch
@@ -65,12 +78,15 @@ public class RenderContext
         };
         };
     }
     }
 
 
-    public RenderContext Clone()
+    public virtual RenderContext Clone()
     {
     {
         return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, DesiredSamplingOptions, Opacity)
         return new RenderContext(RenderSurface, FrameTime, ChunkResolution, RenderOutputSize, DocumentSize, ProcessingColorSpace, DesiredSamplingOptions, Opacity)
         {
         {
             FullRerender = FullRerender,
             FullRerender = FullRerender,
             TargetOutput = TargetOutput,
             TargetOutput = TargetOutput,
+            AffectedArea = AffectedArea,
+            PreviewTextures = PreviewTextures,
+            VisibleDocumentRegion = VisibleDocumentRegion
         };
         };
     }
     }
 }
 }

+ 6 - 12
src/PixiEditor.Extensions.CommonApi/PixiEditor.Extensions.CommonApi.csproj

@@ -24,7 +24,7 @@
     <PropertyGroup>
     <PropertyGroup>
       <ProtogenExists>false</ProtogenExists>
       <ProtogenExists>false</ProtogenExists>
     </PropertyGroup>
     </PropertyGroup>
-    <Exec Command="dotnet tool run protogen --version" IgnoreExitCode="true">
+    <Exec ContinueOnError="true" Command="dotnet tool run protogen --version" IgnoreExitCode="true">
       <Output TaskParameter="ExitCode" PropertyName="ProtogenExitCode"/>
       <Output TaskParameter="ExitCode" PropertyName="ProtogenExitCode"/>
     </Exec>
     </Exec>
     <PropertyGroup>
     <PropertyGroup>
@@ -32,21 +32,15 @@
     </PropertyGroup>
     </PropertyGroup>
   </Target>
   </Target>
 
 
-  <Target Name="InstallProtogen" BeforeTargets="GenerateProtoContracts"
+  <Target Name="WarnProtogen" BeforeTargets="GenerateProtoContracts"
           Condition="'$(ProtogenExists)' != 'true'">
           Condition="'$(ProtogenExists)' != 'true'">
-    <Message Text="Downloading protogen v$(ProtogenVersion)..." Importance="high"/>
-    <Exec Command="dotnet tool install --local protobuf-net.Protogen --version $(ProtogenVersion)"/>
-    <PropertyGroup>
-      <ProtogenExists>true</ProtogenExists>
-    </PropertyGroup>
-
-    <Message Text="protogen installed successfully." Importance="high"/>
+    <Message Text="protogen is not installed. Skipping generating contracts" Importance="high"/>
   </Target>
   </Target>
 
 
-
   <Target Name="GenerateProtoContracts" BeforeTargets="BeforeCompile"
   <Target Name="GenerateProtoContracts" BeforeTargets="BeforeCompile"
-          Inputs="$(MSBuildProjectDirectory)\DataContracts\*.proto"
-          Outputs="$(MSBuildProjectDirectory)\ProtoAutogen\*.cs">
+          Condition="'$(ProtogenExists)' == 'true'"
+    Inputs="$(MSBuildProjectDirectory)\DataContracts\*.proto"
+    Outputs="$(MSBuildProjectDirectory)\ProtoAutogen\*.cs">
     <Exec Command="dotnet tool run protogen --csharp_out=ProtoAutogen --proto_path=DataContracts +listset=yes *.proto"/>
     <Exec Command="dotnet tool run protogen --csharp_out=ProtoAutogen --proto_path=DataContracts +listset=yes *.proto"/>
 
 
     <ItemGroup>
     <ItemGroup>

+ 2 - 0
src/PixiEditor.Extensions.CommonApi/UserPreferences/Settings/PixiEditor/PixiEditorSettings.cs

@@ -28,6 +28,8 @@ public static class PixiEditorSettings
     {
     {
         public static SyncedSetting<bool> EnableSharedToolbar { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
         public static SyncedSetting<bool> EnableSharedToolbar { get; } = SyncedSetting.NonOwned<bool>(PixiEditor);
 
 
+        public static SyncedSetting<bool> SelectionTintingEnabled { get; } = SyncedSetting.NonOwned(PixiEditor, true);
+
         public static SyncedSetting<RightClickMode> RightClickMode { get; } =
         public static SyncedSetting<RightClickMode> RightClickMode { get; } =
             SyncedSetting.NonOwned<RightClickMode>(PixiEditor);
             SyncedSetting.NonOwned<RightClickMode>(PixiEditor);
 
 

+ 1 - 1
src/PixiEditor.Extensions/UI/Overlays/IHandle.cs

@@ -10,6 +10,6 @@ public interface IHandle
     public Paint? StrokePaint { get; set; }
     public Paint? StrokePaint { get; set; }
     public double ZoomScale { get; set; }
     public double ZoomScale { get; set; }
 
 
-    public void Draw(Canvas target);
+    protected void Draw(Canvas target);
     protected void OnPressed(OverlayPointerArgs args);
     protected void OnPressed(OverlayPointerArgs args);
 }
 }

+ 28 - 31
src/PixiEditor.PixiAuth/PixiAuthClient.cs

@@ -42,10 +42,6 @@ public class PixiAuthClient
                 return sessionId;
                 return sessionId;
             }
             }
         }
         }
-        else if (response.StatusCode == HttpStatusCode.BadRequest)
-        {
-            throw new BadRequestException(await response.Content.ReadAsStringAsync());
-        }
         else if (response.StatusCode >= HttpStatusCode.InternalServerError)
         else if (response.StatusCode >= HttpStatusCode.InternalServerError)
         {
         {
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
@@ -68,6 +64,10 @@ public class PixiAuthClient
         {
         {
             throw new ForbiddenException("FORBIDDEN");
             throw new ForbiddenException("FORBIDDEN");
         }
         }
+        else if (response.StatusCode >= HttpStatusCode.BadRequest)
+        {
+            throw new BadRequestException(await response.Content.ReadAsStringAsync());
+        }
 
 
         return null;
         return null;
     }
     }
@@ -97,10 +97,6 @@ public class PixiAuthClient
                 return (token, expirationDate);
                 return (token, expirationDate);
             }
             }
         }
         }
-        else if (response.StatusCode == HttpStatusCode.BadRequest)
-        {
-            throw new BadRequestException(await response.Content.ReadAsStringAsync());
-        }
         else if (response.StatusCode >= HttpStatusCode.InternalServerError)
         else if (response.StatusCode >= HttpStatusCode.InternalServerError)
         {
         {
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
@@ -113,6 +109,10 @@ public class PixiAuthClient
         {
         {
             throw new ForbiddenException("FORBIDDEN");
             throw new ForbiddenException("FORBIDDEN");
         }
         }
+        else if (response.StatusCode >= HttpStatusCode.BadRequest)
+        {
+            throw new BadRequestException(await response.Content.ReadAsStringAsync());
+        }
 
 
         return (null, null);
         return (null, null);
     }
     }
@@ -157,18 +157,14 @@ public class PixiAuthClient
                 return (token, expirationDate);
                 return (token, expirationDate);
             }
             }
         }
         }
-        else if (response.StatusCode == HttpStatusCode.Forbidden)
-        {
-            throw new ForbiddenException("SESSION_NOT_VALID");
-        }
-        else if (response.StatusCode == HttpStatusCode.BadRequest)
-        {
-            throw new BadRequestException(await response.Content.ReadAsStringAsync());
-        }
         else if ((int)response.StatusCode >= 500)
         else if ((int)response.StatusCode >= 500)
         {
         {
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
         }
         }
+        else if (response.StatusCode == HttpStatusCode.Forbidden)
+        {
+            throw new ForbiddenException("SESSION_NOT_VALID");
+        }
         else if (response.StatusCode == HttpStatusCode.Unauthorized)
         else if (response.StatusCode == HttpStatusCode.Unauthorized)
         {
         {
             throw new UnauthorizedAccessException("UNAUTHORIZED");
             throw new UnauthorizedAccessException("UNAUTHORIZED");
@@ -177,6 +173,10 @@ public class PixiAuthClient
         {
         {
             throw new ForbiddenException("FORBIDDEN");
             throw new ForbiddenException("FORBIDDEN");
         }
         }
+        else if (response.StatusCode >= HttpStatusCode.BadRequest)
+        {
+            throw new BadRequestException(await response.Content.ReadAsStringAsync());
+        }
 
 
         return (null, null);
         return (null, null);
     }
     }
@@ -222,15 +222,14 @@ public class PixiAuthClient
 
 
         var response = await httpClient.SendAsync(request);
         var response = await httpClient.SendAsync(request);
 
 
-        if (response.StatusCode == HttpStatusCode.BadRequest)
-        {
-            throw new BadRequestException(await response.Content.ReadAsStringAsync());
-        }
-
         if (response.StatusCode >= HttpStatusCode.InternalServerError)
         if (response.StatusCode >= HttpStatusCode.InternalServerError)
         {
         {
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
         }
         }
+        if (response.StatusCode >= HttpStatusCode.BadRequest)
+        {
+            throw new BadRequestException(await response.Content.ReadAsStringAsync());
+        }
 
 
         if (response.StatusCode == HttpStatusCode.OK)
         if (response.StatusCode == HttpStatusCode.OK)
         {
         {
@@ -256,15 +255,14 @@ public class PixiAuthClient
 
 
         var response = await httpClient.SendAsync(request);
         var response = await httpClient.SendAsync(request);
 
 
-        if (response.StatusCode == HttpStatusCode.BadRequest)
-        {
-            throw new BadRequestException(await response.Content.ReadAsStringAsync());
-        }
-
         if (response.StatusCode >= HttpStatusCode.InternalServerError)
         if (response.StatusCode >= HttpStatusCode.InternalServerError)
         {
         {
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
         }
         }
+        if (response.StatusCode >= HttpStatusCode.BadRequest)
+        {
+            throw new BadRequestException(await response.Content.ReadAsStringAsync());
+        }
 
 
         if (response.StatusCode == HttpStatusCode.OK)
         if (response.StatusCode == HttpStatusCode.OK)
         {
         {
@@ -294,15 +292,14 @@ public class PixiAuthClient
 
 
         var response = await httpClient.SendAsync(request);
         var response = await httpClient.SendAsync(request);
 
 
-        if (response.StatusCode == HttpStatusCode.BadRequest)
-        {
-            throw new BadRequestException(await response.Content.ReadAsStringAsync());
-        }
-
         if (response.StatusCode >= HttpStatusCode.InternalServerError)
         if (response.StatusCode >= HttpStatusCode.InternalServerError)
         {
         {
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
             throw new InternalServerErrorException("INTERNAL_SERVER_ERROR");
         }
         }
+        if (response.StatusCode >= HttpStatusCode.BadRequest)
+        {
+            throw new BadRequestException(await response.Content.ReadAsStringAsync());
+        }
 
 
         if (response.StatusCode == HttpStatusCode.OK)
         if (response.StatusCode == HttpStatusCode.OK)
         {
         {

+ 8 - 1
src/PixiEditor.UI.Common/Controls/TextBox.axaml

@@ -156,7 +156,14 @@
                                    SelectionStart="{TemplateBinding SelectionStart}"
                                    SelectionStart="{TemplateBinding SelectionStart}"
                                    Text="{TemplateBinding Text,Mode=TwoWay}"
                                    Text="{TemplateBinding Text,Mode=TwoWay}"
                                    TextAlignment="{TemplateBinding TextAlignment}"
                                    TextAlignment="{TemplateBinding TextAlignment}"
-                                   TextWrapping="{TemplateBinding TextWrapping}" />
+                                   TextWrapping="{TemplateBinding TextWrapping}">
+                        <TextPresenter.Margin>
+                            <OnPlatform>
+                                <OnPlatform.Default>0</OnPlatform.Default>
+                                <OnPlatform.macOS>2, 0</OnPlatform.macOS>
+                            </OnPlatform>
+                        </TextPresenter.Margin>
+                    </TextPresenter>
                   </Panel>
                   </Panel>
                 </ScrollViewer>
                 </ScrollViewer>
                 <ContentPresenter Grid.Column="2"
                 <ContentPresenter Grid.Column="2"

BIN=BIN
src/PixiEditor.UI.Common/Fonts/PixiPerfect.ttf


+ 3 - 0
src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml

@@ -159,7 +159,9 @@
             <system:String x:Key="icon-select-all">&#xE970;</system:String>
             <system:String x:Key="icon-select-all">&#xE970;</system:String>
             <system:String x:Key="icon-separate-vector">&#xE91C;</system:String>
             <system:String x:Key="icon-separate-vector">&#xE91C;</system:String>
             <system:String x:Key="icon-settings">&#xE971;</system:String>
             <system:String x:Key="icon-settings">&#xE971;</system:String>
+            <system:String x:Key="icon-shredder">&#xE9CA;</system:String>
             <system:String x:Key="icon-shuffle">&#xE992;</system:String>
             <system:String x:Key="icon-shuffle">&#xE992;</system:String>
+            <system:String x:Key="icon-slice">&#xE9CC;</system:String>
             <system:String x:Key="icon-sliders">&#xE972;</system:String>
             <system:String x:Key="icon-sliders">&#xE972;</system:String>
             <system:String x:Key="icon-snapping">&#xE9A7;</system:String>
             <system:String x:Key="icon-snapping">&#xE9A7;</system:String>
             <system:String x:Key="icon-spline-chart">&#xE9BE;</system:String>
             <system:String x:Key="icon-spline-chart">&#xE9BE;</system:String>
@@ -198,6 +200,7 @@
             <system:String x:Key="icon-upload-cloud">&#xE98E;</system:String>
             <system:String x:Key="icon-upload-cloud">&#xE98E;</system:String>
             <system:String x:Key="icon-user">&#xE97B;</system:String>
             <system:String x:Key="icon-user">&#xE97B;</system:String>
             <system:String x:Key="icon-vector-pen">&#xE965;</system:String>
             <system:String x:Key="icon-vector-pen">&#xE965;</system:String>
+            <system:String x:Key="icon-whole-word">&#xE9CB;</system:String>
             <system:String x:Key="icon-write">&#xE988;</system:String>
             <system:String x:Key="icon-write">&#xE988;</system:String>
             <system:String x:Key="icon-x-flip">&#xE900;</system:String>
             <system:String x:Key="icon-x-flip">&#xE900;</system:String>
             <system:String x:Key="icon-x-symmetry">&#xE980;</system:String>
             <system:String x:Key="icon-x-symmetry">&#xE980;</system:String>

+ 3 - 0
src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml.cs

@@ -155,7 +155,9 @@ public static partial class PixiPerfectIcons
     public const string SelectAll = "\uE970";
     public const string SelectAll = "\uE970";
     public const string SeparateVector = "\uE91C";
     public const string SeparateVector = "\uE91C";
     public const string Settings = "\uE971";
     public const string Settings = "\uE971";
+    public const string Shredder = "\uE9CA";
     public const string Shuffle = "\uE992";
     public const string Shuffle = "\uE992";
+    public const string Slice = "\uE9CC";
     public const string Sliders = "\uE972";
     public const string Sliders = "\uE972";
     public const string Snapping = "\uE9A7";
     public const string Snapping = "\uE9A7";
     public const string SplineChart = "\uE9BE";
     public const string SplineChart = "\uE9BE";
@@ -194,6 +196,7 @@ public static partial class PixiPerfectIcons
     public const string UploadCloud = "\uE98E";
     public const string UploadCloud = "\uE98E";
     public const string User = "\uE97B";
     public const string User = "\uE97B";
     public const string VectorPen = "\uE965";
     public const string VectorPen = "\uE965";
+    public const string WholeWord = "\uE9CB";
     public const string Write = "\uE988";
     public const string Write = "\uE988";
     public const string XFlip = "\uE900";
     public const string XFlip = "\uE900";
     public const string XSymmetry = "\uE980";
     public const string XSymmetry = "\uE980";

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2 - 2
src/PixiEditor.UI.Common/Fonts/defs.svg


+ 0 - 1
src/PixiEditor.sln

@@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildConfiguration", "Build
 	ProjectSection(SolutionItems) = preProject
 	ProjectSection(SolutionItems) = preProject
 		Custom.ruleset = Custom.ruleset
 		Custom.ruleset = Custom.ruleset
 		Directory.Build.props = Directory.Build.props
 		Directory.Build.props = Directory.Build.props
-		stylecop.json = stylecop.json
 	EndProjectSection
 	EndProjectSection
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLib", "ChunkyImageLib\ChunkyImageLib.csproj", "{6A9DA760-1E47-414C-B8E8-3B4927F18131}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLib", "ChunkyImageLib\ChunkyImageLib.csproj", "{6A9DA760-1E47-414C-B8E8-3B4927F18131}"

+ 18 - 3
src/PixiEditor/Data/Configs/ToolSetsConfig.json

@@ -5,7 +5,12 @@
     "Tools": [
     "Tools": [
       "MoveViewport",
       "MoveViewport",
       "RotateViewport",
       "RotateViewport",
-      "Move",
+      {
+        "ToolName": "Move",
+        "Settings": {
+          "BilinearTransform": false
+        }
+      },
       {
       {
         "ToolName": "Pen",
         "ToolName": "Pen",
         "Settings": {
         "Settings": {
@@ -52,7 +57,12 @@
     "Tools": [
     "Tools": [
       "MoveViewport",
       "MoveViewport",
       "RotateViewport",
       "RotateViewport",
-      "Move",
+      {
+        "ToolName": "Move",
+        "Settings": {
+          "BilinearTransform": true
+        }
+      },
       {
       {
         "ToolName": "Pen",
         "ToolName": "Pen",
         "Settings": {
         "Settings": {
@@ -127,7 +137,12 @@
     "Tools": [
     "Tools": [
       "MoveViewport",
       "MoveViewport",
       "RotateViewport",
       "RotateViewport",
-      "Move",
+      {
+        "ToolName": "Move",
+        "Settings": {
+          "BilinearTransform": true
+        }
+      },
       "VectorPath",
       "VectorPath",
       "VectorLine",
       "VectorLine",
       "VectorEllipse",
       "VectorEllipse",

+ 663 - 216
src/PixiEditor/Data/Localization/Languages/ar.json

@@ -8,16 +8,16 @@
   "DISCORD": "ديسكورد",
   "DISCORD": "ديسكورد",
   "KEY_BINDINGS": "ارتباطات المفاتيح",
   "KEY_BINDINGS": "ارتباطات المفاتيح",
   "MISC": "متنوع",
   "MISC": "متنوع",
-  "SHOW_STARTUP_WINDOW": "عرض نافذة بدء التشغيل",
+  "SHOW_STARTUP_WINDOW": "عرض نافذة بدئ التشغيل",
   "RECENT_FILE_LENGTH": "طول قائمة الملفات الاخيرة",
   "RECENT_FILE_LENGTH": "طول قائمة الملفات الاخيرة",
   "RECENT_FILE_LENGTH_TOOLTIP": "كم عدد المستندات التي يتم اضهارها في ملف > الملفات الاخيرة. الافتراضي: 8",
   "RECENT_FILE_LENGTH_TOOLTIP": "كم عدد المستندات التي يتم اضهارها في ملف > الملفات الاخيرة. الافتراضي: 8",
   "DEFAULT_NEW_SIZE": "الحجم الافتراضي للملف الجديد",
   "DEFAULT_NEW_SIZE": "الحجم الافتراضي للملف الجديد",
   "WIDTH": "العرض",
   "WIDTH": "العرض",
   "HEIGHT": "الطول",
   "HEIGHT": "الطول",
-  "TOOLS": "ألادوات",
+  "TOOLS": "الأدوات",
   "ENABLE_SHARED_TOOLBAR": "تمكين شريط الادوات المشترك",
   "ENABLE_SHARED_TOOLBAR": "تمكين شريط الادوات المشترك",
   "AUTOMATIC_UPDATES": "التحديثات التلقائية",
   "AUTOMATIC_UPDATES": "التحديثات التلقائية",
-  "CHECK_FOR_UPDATES": "تحقق من التحديثات عند بدء التشغيل",
+  "CHECK_FOR_UPDATES": "تحقق من التحديثات عند بدئ التشغيل",
   "UPDATE_STREAM": "مصدر التحديث",
   "UPDATE_STREAM": "مصدر التحديث",
   "UPDATE_CHANNEL_HELP_TOOLTIP": "لا يمكن تغيير قنوات التحديث إلا في إصدار مستقل (يتم تنزيله من https://pixieditor.net).\nتتعامل إصدارات Steam و Microsoft Store مع التحديثات بشكل منفصل.",
   "UPDATE_CHANNEL_HELP_TOOLTIP": "لا يمكن تغيير قنوات التحديث إلا في إصدار مستقل (يتم تنزيله من https://pixieditor.net).\nتتعامل إصدارات Steam و Microsoft Store مع التحديثات بشكل منفصل.",
   "DEBUG": "معالجة",
   "DEBUG": "معالجة",
@@ -25,14 +25,14 @@
   "OPEN_CRASH_REPORTS_DIR": "افتح دليل تقارير الاعطال",
   "OPEN_CRASH_REPORTS_DIR": "افتح دليل تقارير الاعطال",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "ENABLED": "مفعل",
   "ENABLED": "مفعل",
-  "SHOW_IMAGE_NAME": "اضهار اسم الصورة",
-  "SHOW_IMAGE_SIZE": "اضهار حجم الصورة",
-  "SHOW_LAYER_COUNT": "اضهار عدد الطبقات",
+  "SHOW_IMAGE_NAME": "إظهار اسم الصورة",
+  "SHOW_IMAGE_SIZE": "إظهار حجم الصورة",
+  "SHOW_LAYER_COUNT": "إظهار عدد الطبقات",
   "FILE": "ملف",
   "FILE": "ملف",
   "RECENT": "مؤخرًا",
   "RECENT": "مؤخرًا",
   "OPEN": "فتح",
   "OPEN": "فتح",
-  "SAVE_PIXI": "حفض ( .pixi )",
-  "SAVE_AS_PIXI": "حفض جديد ك ( .pixi )",
+  "SAVE_PIXI": "حفظ ( .pixi )",
+  "SAVE_AS_PIXI": "حفظ جديد كـ( .pixi )",
   "EXPORT_IMG": "تصدير (png, .jpg., الخ.)",
   "EXPORT_IMG": "تصدير (png, .jpg., الخ.)",
   "EDIT": "تعديل",
   "EDIT": "تعديل",
   "EXIT": "خروج",
   "EXIT": "خروج",
@@ -42,7 +42,7 @@
   "ANCHOR_POINT": "نقاط الربط",
   "ANCHOR_POINT": "نقاط الربط",
   "RESIZE_IMAGE": "تغيير حجم الصورة",
   "RESIZE_IMAGE": "تغيير حجم الصورة",
   "RESIZE": "تغيير الحجم",
   "RESIZE": "تغيير الحجم",
-  "DOCUMENTATION": "التعليمات",
+  "DOCUMENTATION": "الوثائق",
   "WEBSITE": "الموقع",
   "WEBSITE": "الموقع",
   "OPEN_WEBSITE": "فتح الموقع",
   "OPEN_WEBSITE": "فتح الموقع",
   "REPOSITORY": "Repository",
   "REPOSITORY": "Repository",
@@ -56,11 +56,8 @@
   "DECREASE_TOOL_SIZE": "تقليل حجم الأداة",
   "DECREASE_TOOL_SIZE": "تقليل حجم الأداة",
   "DOWNLOADING_UPDATE": "تحميل التحديث...",
   "DOWNLOADING_UPDATE": "تحميل التحديث...",
   "UPDATE_READY": "التحديث جاهز للتثبيت. هل تريد تثبيته الآن؟",
   "UPDATE_READY": "التحديث جاهز للتثبيت. هل تريد تثبيته الآن؟",
-  "NEW_UPDATE": "تحديث جديد",
   "COULD_NOT_UPDATE_WITHOUT_ADMIN": "تعذر التحديث بدون امتيازات المسؤول. الرجاء تشغيل PixiEditor كمسؤول.",
   "COULD_NOT_UPDATE_WITHOUT_ADMIN": "تعذر التحديث بدون امتيازات المسؤول. الرجاء تشغيل PixiEditor كمسؤول.",
   "INSUFFICIENT_PERMISSIONS": "أذونات غير كافية",
   "INSUFFICIENT_PERMISSIONS": "أذونات غير كافية",
-  "UPDATE_CHECK_FAILED": "فشل التحقق من التحديث",
-  "COULD_NOT_CHECK_FOR_UPDATES": "تعذر التحقق مما إذا كان هناك تحديث متوفر.",
   "VERSION": "الاصدار {0}",
   "VERSION": "الاصدار {0}",
   "OPEN_TEMP_DIR": "افتح الدليل temp",
   "OPEN_TEMP_DIR": "افتح الدليل temp",
   "OPEN_LOCAL_APPDATA_DIR": "افتح دليل Local AppData",
   "OPEN_LOCAL_APPDATA_DIR": "افتح دليل Local AppData",
@@ -85,7 +82,7 @@
   "PATH_DOES_NOT_EXIST": "{0} غير موجود.",
   "PATH_DOES_NOT_EXIST": "{0} غير موجود.",
   "LOCATION_DOES_NOT_EXIST": "الموقع غير موجود.",
   "LOCATION_DOES_NOT_EXIST": "الموقع غير موجود.",
   "FILE_NOT_FOUND": "لم يتم العثور على الملف.",
   "FILE_NOT_FOUND": "لم يتم العثور على الملف.",
-  "ARE_YOU_SURE": "هل انت متاكد؟",
+  "ARE_YOU_SURE": "هل انت متأكد؟",
   "ARE_YOU_SURE_PATH_FULL_PATH": "هل أنت متأكد أنك تريد حذف {0}؟\nستفقد هذه البيانات لجميع التركيبات.\n(مسار كامل: {1})",
   "ARE_YOU_SURE_PATH_FULL_PATH": "هل أنت متأكد أنك تريد حذف {0}؟\nستفقد هذه البيانات لجميع التركيبات.\n(مسار كامل: {1})",
   "FAILED_TO_OPEN_FILE": "فشل في فتح الملف",
   "FAILED_TO_OPEN_FILE": "فشل في فتح الملف",
   "OLD_FILE_FORMAT": "تنسيق الملف القديم",
   "OLD_FILE_FORMAT": "تنسيق الملف القديم",
@@ -93,49 +90,46 @@
   "NOTHING_FOUND": "لم يتم العثور على شيء",
   "NOTHING_FOUND": "لم يتم العثور على شيء",
   "EXPORT": "تصدير",
   "EXPORT": "تصدير",
   "EXPORT_IMAGE": "تصدير الصورة",
   "EXPORT_IMAGE": "تصدير الصورة",
-  "IMPORT": "اضافة",
-  "SHORTCUT_TEMPLATES": "اختصارات القوالب",
+  "IMPORT": "إضافة",
+  "SHORTCUT_TEMPLATES": "إختصارات القوالب",
   "RESET_ALL": "إعادة ضبط الكل",
   "RESET_ALL": "إعادة ضبط الكل",
   "LAYER": "طبقة",
   "LAYER": "طبقة",
-  "LAYER_DELETE_SELECTED": "حذف الطبقة/المجلد النشط",
-  "LAYER_DELETE_SELECTED_DESCRIPTIVE": "حذف الطبقة أو المجلد النشط",
-  "LAYER_DELETE_ALL_SELECTED": "احذف جميع الطبقات / المجلدات المحددة",
-  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "احذف كل الطبقات و / أو المجلدات المحددة",
-  "DELETE_SELECTED_PIXELS": "حذف البكسلات المحددة",
+  "LAYER_DELETE_ALL_SELECTED": "حذف جميع الطبقات / المجلدات المحددة",
+  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "حذف كل الطبقات و / أو المجلدات المحددة",
   "NEW_FOLDER": "مجلد جديد",
   "NEW_FOLDER": "مجلد جديد",
   "CREATE_NEW_FOLDER": "انشاء مجلد جديد",
   "CREATE_NEW_FOLDER": "انشاء مجلد جديد",
   "NEW_LAYER": "طبقة جديدة",
   "NEW_LAYER": "طبقة جديدة",
   "CREATE_NEW_LAYER": "انشاء طبقة جديدة",
   "CREATE_NEW_LAYER": "انشاء طبقة جديدة",
   "NEW_IMAGE": "صورة جديدة",
   "NEW_IMAGE": "صورة جديدة",
   "CREATE_NEW_IMAGE": "انشاء صورة جديدة",
   "CREATE_NEW_IMAGE": "انشاء صورة جديدة",
-  "SAVE": "حفض",
-  "SAVE_AS": "حفض جديد...",
+  "SAVE": "حفظ",
+  "SAVE_AS": "حفظ جديد...",
   "IMAGE": "صورة",
   "IMAGE": "صورة",
-  "SAVE_IMAGE": "حفض الصورة",
-  "SAVE_IMAGE_AS": "حفض الصورة كجديدة",
+  "SAVE_IMAGE": "حفظ الصورة",
+  "SAVE_IMAGE_AS": "حفظ الصورة كجديدة",
   "DUPLICATE": "تكرار",
   "DUPLICATE": "تكرار",
   "DUPLICATE_SELECTED_LAYER": "تكرار الطبقة المجددة",
   "DUPLICATE_SELECTED_LAYER": "تكرار الطبقة المجددة",
-  "CREATE_MASK": "انشاء قناع",
-  "DELETE_MASK": "حذف قناع",
-  "TOGGLE_MASK": "تبديل القناع",
+  "CREATE_MASK": "إنشاء قناع",
+  "DELETE_MASK": "حذف القناع",
+  "TOGGLE_MASK": "تفعيل/إبطال القناع",
   "APPLY_MASK": "تطبيق القناع",
   "APPLY_MASK": "تطبيق القناع",
-  "TOGGLE_VISIBILITY": "تبديل الرؤية",
-  "MOVE_MEMBER_UP": "حرك العضو لأعلى",
-  "MOVE_MEMBER_UP_DESCRIPTIVE": "انقل الطبقة أو المجلد المحدد لأعلى",
-  "MOVE_MEMBER_DOWN": "حرك العضو لأسفل",
-  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "انقل الطبقة أو المجلد المحدد لأسفل",
+  "TOGGLE_VISIBILITY": "تفعيل/إبطال الرؤية",
+  "MOVE_MEMBER_UP": "تحريك العضو لأعلى",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "نقل الطبقة أو المجلد المحدد لأعلى",
+  "MOVE_MEMBER_DOWN": "تحريك العضو لأسفل",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "نقل الطبقة أو المجلد المحدد لأسفل",
   "MERGE_ALL_SELECTED_LAYERS": "دمج كل الطبقات المحددة",
   "MERGE_ALL_SELECTED_LAYERS": "دمج كل الطبقات المحددة",
-  "MERGE_WITH_ABOVE": "دمج الطبقة المحددة أعلاه",
+  "MERGE_WITH_ABOVE": "دمج الطبقة المحددة مع أعلاها",
   "MERGE_WITH_ABOVE_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة التي فوقها",
   "MERGE_WITH_ABOVE_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة التي فوقها",
-  "MERGE_WITH_BELOW": "دمج الطبقة المحددة أدناه",
+  "MERGE_WITH_BELOW": "دمج الطبقة المحددة مع أدناها",
   "MERGE_WITH_BELOW_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة الموجودة تحتها",
   "MERGE_WITH_BELOW_DESCRIPTIVE": "دمج الطبقة المحددة مع الطبقة الموجودة تحتها",
-  "ADD_REFERENCE_LAYER": "أضف طبقة مرجعية",
-  "DELETE_REFERENCE_LAYER": "احذف الطبقة المرجعية",
+  "ADD_REFERENCE_LAYER": "إضافة طبقة مرجعية",
+  "DELETE_REFERENCE_LAYER": "حذف الطبقة المرجعية",
   "TRANSFORM_REFERENCE_LAYER": "تحويل الطبقة المرجعية",
   "TRANSFORM_REFERENCE_LAYER": "تحويل الطبقة المرجعية",
-  "TOGGLE_REFERENCE_LAYER_POS": "تبديل موضع الطبقة المرجعية",
-  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "تبديل الطبقة المرجعية بين الأعلى أو الأكثر أدناه",
+  "TOGGLE_REFERENCE_LAYER_POS": "تفعيل/إبطال موضع الطبقة المرجعية",
+  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "تبديل الطبقة المرجعية بين الأعلى أو الأدنى",
   "RESET_REFERENCE_LAYER_POS": "إعادة تعيين موضع الطبقة المرجعية",
   "RESET_REFERENCE_LAYER_POS": "إعادة تعيين موضع الطبقة المرجعية",
-  "CLIP_CANVAS": "مقطع الصورة",
+  "CLIP_CANVAS": "حف الصورة",
   "FLIP_IMG_VERTICALLY": "قلب الصورة عموديًا",
   "FLIP_IMG_VERTICALLY": "قلب الصورة عموديًا",
   "FLIP_IMG_HORIZONTALLY": "قلب الصورة أفقيًا",
   "FLIP_IMG_HORIZONTALLY": "قلب الصورة أفقيًا",
   "FLIP_LAYERS_VERTICALLY": "قلب الطبقات المحددة عموديًا",
   "FLIP_LAYERS_VERTICALLY": "قلب الطبقات المحددة عموديًا",
@@ -146,43 +140,42 @@
   "ROT_LAYERS_90": "تدوير الطبقات المحددة 90 درجة",
   "ROT_LAYERS_90": "تدوير الطبقات المحددة 90 درجة",
   "ROT_LAYERS_180": "تدوير الطبقات المحددة 180 درجة",
   "ROT_LAYERS_180": "تدوير الطبقات المحددة 180 درجة",
   "ROT_LAYERS_-90": "تدوير الطبقات المحددة -90 درجة",
   "ROT_LAYERS_-90": "تدوير الطبقات المحددة -90 درجة",
-  "TOGGLE_VERT_SYMMETRY_AXIS": "تبديل محور التناظر العمودي",
-  "TOGGLE_HOR_SYMMETRY_AXIS": "تبديل محور التناظر الأفقي",
-  "RESIZE_DOCUMENT": "تغيير حجم العنصر",
-  "RESIZE_CANVAS": "تغيير حجم الصورة",
+  "TOGGLE_VERT_SYMMETRY_AXIS": "تفعيل/إبطال محور التناظر العمودي",
+  "TOGGLE_HOR_SYMMETRY_AXIS": "تفعيل/إبطال محور التناظر الأفقي",
+  "RESIZE_DOCUMENT": "تغيير حجم المستند",
+  "RESIZE_CANVAS": "تغيير حجم اللوحة",
   "CENTER_CONTENT": "توسيط المحتوى",
   "CENTER_CONTENT": "توسيط المحتوى",
   "CUT": "قص",
   "CUT": "قص",
   "CUT_DESCRIPTIVE": "قص المنطقة/الطبقات المحددة",
   "CUT_DESCRIPTIVE": "قص المنطقة/الطبقات المحددة",
   "PASTE": "لصق",
   "PASTE": "لصق",
   "PASTE_DESCRIPTIVE": "لصق محتويات الحافظة",
   "PASTE_DESCRIPTIVE": "لصق محتويات الحافظة",
   "PASTE_AS_NEW_LAYER": "لصق كطبقة جديدة",
   "PASTE_AS_NEW_LAYER": "لصق كطبقة جديدة",
-  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "لصق من الحافضةكطبقة جديدة",
+  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "لصق من الحافظة كطبقة جديدة",
   "PASTE_REFERENCE_LAYER": "لصق الطبقة المرجعية",
   "PASTE_REFERENCE_LAYER": "لصق الطبقة المرجعية",
   "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "لصق محتويات الحافظة كطبقة مرجعية",
   "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "لصق محتويات الحافظة كطبقة مرجعية",
   "PASTE_COLOR": "لصق اللون",
   "PASTE_COLOR": "لصق اللون",
   "PASTE_COLOR_DESCRIPTIVE": "لصق اللون من الحافظة",
   "PASTE_COLOR_DESCRIPTIVE": "لصق اللون من الحافظة",
-  "PASTE_COLOR_SECONDARY": "لصق اللون على أنه ثانوي",
-  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "الصق اللون من الحافظة كلون ثانوي",
+  "PASTE_COLOR_SECONDARY": "لصق اللون كلون ثانوي",
+  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "لصق اللون من الحافظة كلون ثانوي",
   "CLIPBOARD": "الحافظة",
   "CLIPBOARD": "الحافظة",
   "COPY": "نسخ",
   "COPY": "نسخ",
-  "COPY_DESCRIPTIVE": "نسخ الي الحافضة",
+  "COPY_DESCRIPTIVE": "نسخ الي الحافظة",
   "COPY_COLOR_HEX": "نسخ اللون الاساسي (HEX)",
   "COPY_COLOR_HEX": "نسخ اللون الاساسي (HEX)",
-  "COPY_COLOR_HEX_DESCRIPTIVE": "نسخ اللون الاساسي ك HEX",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "نسخ اللون الاساسي كـ HEX",
   "COPY_COLOR_RGB": "نسخ اللون الاساسي (RGB)",
   "COPY_COLOR_RGB": "نسخ اللون الاساسي (RGB)",
-  "COPY_COLOR_RGB_DESCRIPTIVE": "نسخ اللون الاساسي ك RGB",
+  "COPY_COLOR_RGB_DESCRIPTIVE": "نسخ اللون الاساسي كـ RGB",
   "COPY_COLOR_SECONDARY_HEX": "نسخ اللون الثانوي (HEX)",
   "COPY_COLOR_SECONDARY_HEX": "نسخ اللون الثانوي (HEX)",
-  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "نسخ اللون الثانوي ك HEX",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "نسخ اللون الثانوي كـ HEX",
   "COPY_COLOR_SECONDARY_RGB": "نسخ اللون الثانوي (RGB)",
   "COPY_COLOR_SECONDARY_RGB": "نسخ اللون الثانوي (RGB)",
-  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "نسخ اللون الثانوي ك RGB",
-  "PALETTE_COLORS": "لوحة الالوان",
-  "REPLACE_SECONDARY_BY_PRIMARY": "استبدل اللون الثانوي باللون الأساسي",
-  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "استبدل اللون الثانوي باللون الأساسي",
-  "REPLACE_PRIMARY_BY_SECONDARY": "استبدل اللون الأساسي باللون الثانوي",
-  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "استبدل اللون الأساسي باللون الثانوي",
-  "OPEN_PALETTE_BROWSER": "افتح متصفح لوحة الألوان",
+  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "نسخ اللون الثانوي كـ RGB",
+  "PALETTE_COLORS": "لوحة الألوان",
+  "REPLACE_SECONDARY_BY_PRIMARY": "استبدال اللون الأساسي بالثانوي",
+  "REPLACE_PRIMARY_BY_SECONDARY": "استبدال اللون الثانوي بالأساسي",
+  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "استبدال اللون الثانوي باللون الأساسي",
+  "OPEN_PALETTE_BROWSER": "فتح متصفح لوحة الألوان",
   "OVERWRITE_PALETTE_CONSENT": "اللوحة '{0}' موجودة بالفعل ، هل تريد استبدالها؟",
   "OVERWRITE_PALETTE_CONSENT": "اللوحة '{0}' موجودة بالفعل ، هل تريد استبدالها؟",
   "PALETTE_EXISTS": "لوح الألوان موجود بالفعل",
   "PALETTE_EXISTS": "لوح الألوان موجود بالفعل",
-  "REPLACE_PALETTE_CONSENT": "هل تريد استبدال اللوحة الحالية باللوحة المحددة؟",
+  "REPLACE_PALETTE_CONSENT": "هل تريد استبدال اللوحة المحددة باللوحة الحالية؟",
   "REPLACE_PALETTE": "استبدال اللوحة الحالية",
   "REPLACE_PALETTE": "استبدال اللوحة الحالية",
   "SELECT_COLOR_1": "حدد اللون 1",
   "SELECT_COLOR_1": "حدد اللون 1",
   "SELECT_COLOR_2": "حدد اللون 2",
   "SELECT_COLOR_2": "حدد اللون 2",
@@ -194,7 +187,7 @@
   "SELECT_COLOR_8": "حدد اللون 8",
   "SELECT_COLOR_8": "حدد اللون 8",
   "SELECT_COLOR_9": "حدد اللون 9",
   "SELECT_COLOR_9": "حدد اللون 9",
   "SELECT_COLOR_10": "حدد اللون 10",
   "SELECT_COLOR_10": "حدد اللون 10",
-  "SELECT_TOOL": "حدد الاداة {0}",
+  "SELECT_TOOL": "حدد الأداة {0}",
   "SELECT_COLOR_1_DESCRIPTIVE": "حدد اللون الأول في اللوحة",
   "SELECT_COLOR_1_DESCRIPTIVE": "حدد اللون الأول في اللوحة",
   "SELECT_COLOR_2_DESCRIPTIVE": "حدد اللون الثاني في اللوحة",
   "SELECT_COLOR_2_DESCRIPTIVE": "حدد اللون الثاني في اللوحة",
   "SELECT_COLOR_3_DESCRIPTIVE": "حدد اللون الثالث في اللوحة",
   "SELECT_COLOR_3_DESCRIPTIVE": "حدد اللون الثالث في اللوحة",
@@ -208,23 +201,23 @@
   "SWAP_COLORS": "تبديل الألوان",
   "SWAP_COLORS": "تبديل الألوان",
   "SWAP_COLORS_DESCRIPTIVE": "تبديل الألوان الأساسية والثانوية",
   "SWAP_COLORS_DESCRIPTIVE": "تبديل الألوان الأساسية والثانوية",
   "SEARCH": "بحث",
   "SEARCH": "بحث",
-  "COMMAND_SEARCH": "اوامر البحث",
-  "OPEN_COMMAND_SEARCH": "فتح نافذة اوامر البحث",
+  "COMMAND_SEARCH": "أوامر البحث",
+  "OPEN_COMMAND_SEARCH": "فتح نافذة أوامر البحث",
   "SELECT": "حدد",
   "SELECT": "حدد",
-  "DESELECT": "الغاء التحديد",
+  "DESELECT": "إلغاء التحديد",
   "INVERT": "عكس",
   "INVERT": "عكس",
-  "SELECTION": "اختيار",
-  "SELECT_ALL": "حدد الكل",
-  "SELECT_ALL_DESCRIPTIVE": "حدد كل شيء",
+  "SELECTION": "تحديد",
+  "SELECT_ALL": "تحديد الكل",
+  "SELECT_ALL_DESCRIPTIVE": "تحديد كل شيء",
   "CLEAR_SELECTION": "حذف المحدد",
   "CLEAR_SELECTION": "حذف المحدد",
-  "INVERT_SELECTION": "اقلب المحدد",
-  "INVERT_SELECTION_DESCRIPTIVE": "اقلب المنطقة المحددة",
+  "INVERT_SELECTION": "عكس المحدد",
+  "INVERT_SELECTION_DESCRIPTIVE": "عكس المنطقة المحددة",
   "TRANSFORM_SELECTED_AREA": "تحويل المنطقة المحددة",
   "TRANSFORM_SELECTED_AREA": "تحويل المنطقة المحددة",
   "NUDGE_SELECTED_LEFT": "دفع العنصر المحدد لليسار",
   "NUDGE_SELECTED_LEFT": "دفع العنصر المحدد لليسار",
   "NUDGE_SELECTED_RIGHT": "دفع العنصر المحدد لليمين",
   "NUDGE_SELECTED_RIGHT": "دفع العنصر المحدد لليمين",
   "NUDGE_SELECTED_UP": "دفع العنصر المحدد لأعلى",
   "NUDGE_SELECTED_UP": "دفع العنصر المحدد لأعلى",
   "NUDGE_SELECTED_DOWN": "دفع العنصر المحدد لأسفل",
   "NUDGE_SELECTED_DOWN": "دفع العنصر المحدد لأسفل",
-  "MASK_FROM_SELECTION": "قناع جديد من الاختيار",
+  "MASK_FROM_SELECTION": "قناع جديد من المحدد",
   "MASK_FROM_SELECTION_DESCRIPTIVE": "التحديد لقناع جديد",
   "MASK_FROM_SELECTION_DESCRIPTIVE": "التحديد لقناع جديد",
   "ADD_SELECTION_TO_MASK": "إضافة التحديد إلى القناع",
   "ADD_SELECTION_TO_MASK": "إضافة التحديد إلى القناع",
   "SUBTRACT_SELECTION_FROM_MASK": "قطع التحديد من القناع",
   "SUBTRACT_SELECTION_FROM_MASK": "قطع التحديد من القناع",
@@ -232,27 +225,27 @@
   "SELECTION_TO_MASK": "التحديد للقناع",
   "SELECTION_TO_MASK": "التحديد للقناع",
   "TO_NEW_MASK": "إلى قناع جديد",
   "TO_NEW_MASK": "إلى قناع جديد",
   "ADD_TO_MASK": "أضف إلى القناع",
   "ADD_TO_MASK": "أضف إلى القناع",
-  "SUBTRACT_FROM_MASK": "قص من القناع",
-  "INTERSECT_WITH_MASK": "تتقاطع مع القناع",
+  "SUBTRACT_FROM_MASK": "إنقاص من القناع",
+  "INTERSECT_WITH_MASK": "تقاطع مع القناع",
   "STYLUS": "قلم",
   "STYLUS": "قلم",
-  "TOGGLE_PEN_MODE": "تبديل وضع القلم",
+  "TOGGLE_PEN_MODE": "تفعيل/إبطال وضع القلم",
   "UNDO": "تراجع",
   "UNDO": "تراجع",
   "UNDO_DESCRIPTIVE": "تراجع عن الإجراء الأخير",
   "UNDO_DESCRIPTIVE": "تراجع عن الإجراء الأخير",
   "REDO": "إعادة",
   "REDO": "إعادة",
   "REDO_DESCRIPTIVE": "إعادة الإجراء الأخير",
   "REDO_DESCRIPTIVE": "إعادة الإجراء الأخير",
   "WINDOWS": "النوافذ",
   "WINDOWS": "النوافذ",
-  "TOGGLE_GRIDLINES": "تبديل خطوط الشبكة",
+  "TOGGLE_GRIDLINES": "تفعيل/إبطال خطوط الشبكة",
   "ZOOM_IN": "تكبير",
   "ZOOM_IN": "تكبير",
   "ZOOM_OUT": "تصغير",
   "ZOOM_OUT": "تصغير",
   "NEW_WINDOW_FOR_IMG": "نافذة جديدة للصورة الحالية",
   "NEW_WINDOW_FOR_IMG": "نافذة جديدة للصورة الحالية",
   "CENTER_ACTIVE_VIEWPORT": "توسيط إطار العرض النشط",
   "CENTER_ACTIVE_VIEWPORT": "توسيط إطار العرض النشط",
   "FLIP_VIEWPORT_HORIZONTALLY": "قلب إطار العرض أفقيًا",
   "FLIP_VIEWPORT_HORIZONTALLY": "قلب إطار العرض أفقيًا",
-  "FLIP_VIEWPORT_VERTICALLY": "قلب إطار العرض رأسيًا",
+  "FLIP_VIEWPORT_VERTICALLY": "قلب إطار العرض عموديًا",
   "SETTINGS": "الاعدادات",
   "SETTINGS": "الاعدادات",
   "OPEN_SETTINGS": "فتح الاعدادات",
   "OPEN_SETTINGS": "فتح الاعدادات",
   "OPEN_SETTINGS_DESCRIPTIVE": "فتح نافذة الاعدادات",
   "OPEN_SETTINGS_DESCRIPTIVE": "فتح نافذة الاعدادات",
   "OPEN_STARTUP_WINDOW": "فتح نافذة التشغيل",
   "OPEN_STARTUP_WINDOW": "فتح نافذة التشغيل",
-  "OPEN_SHORTCUT_WINDOW": "افتح نافذة الاختصارات",
+  "OPEN_SHORTCUT_WINDOW": "فتح نافذة الاختصارات",
   "OPEN_ABOUT_WINDOW": "فتح نافذة المعلومات",
   "OPEN_ABOUT_WINDOW": "فتح نافذة المعلومات",
   "ERROR": "خطأ",
   "ERROR": "خطأ",
   "INTERNAL_ERROR": "خطأ داخلي",
   "INTERNAL_ERROR": "خطأ داخلي",
@@ -266,51 +259,51 @@
   "DONATE": "تبرع",
   "DONATE": "تبرع",
   "YES": "نعم",
   "YES": "نعم",
   "NO": "لا",
   "NO": "لا",
-  "CANCEL": "الغاء",
+  "CANCEL": "إلغاء",
   "UNNAMED": "بدون اسم",
   "UNNAMED": "بدون اسم",
-  "OPEN_COMMAND_DEBUG_WINDOW": "افتح نافذة تصحيح أخطاء الأمر",
+  "OPEN_COMMAND_DEBUG_WINDOW": "افتح نافذة معالجة أخطاء الأمر",
   "DELETE": "حذف",
   "DELETE": "حذف",
   "USER_PREFS": "تفضيلات المستخدم (Roaming)",
   "USER_PREFS": "تفضيلات المستخدم (Roaming)",
-  "SHORTCUT_FILE": "اختصار الملف (Roaming)",
+  "SHORTCUT_FILE": "ملف الاختصارات (Roaming)",
   "EDITOR_DATA": "بيانات البرنامج (Local)",
   "EDITOR_DATA": "بيانات البرنامج (Local)",
   "MOVE_VIEWPORT_TOOLTIP": "ينقل إطار العرض. ({0})",
   "MOVE_VIEWPORT_TOOLTIP": "ينقل إطار العرض. ({0})",
-  "MOVE_VIEWPORT_ACTION_DISPLAY": "انقر وانتقل لتحريك منفذ العرض",
+  "MOVE_VIEWPORT_ACTION_DISPLAY": "انقر وانتقل لتحريك إطار العرض",
   "MOVE_TOOL_TOOLTIP": "ينقل وحدات البكسل المحددة ({0}). اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "MOVE_TOOL_TOOLTIP": "ينقل وحدات البكسل المحددة ({0}). اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "MOVE_TOOL_ACTION_DISPLAY": "استمر في الضغط على الماوس لتحريك وحدات البكسل المحددة. اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "MOVE_TOOL_ACTION_DISPLAY": "استمر في الضغط على الماوس لتحريك وحدات البكسل المحددة. اضغط مع الاستمرار على Ctrl لتحريك كل الطبقات.",
   "PEN_TOOL_TOOLTIP": "قلم. ({0})",
   "PEN_TOOL_TOOLTIP": "قلم. ({0})",
   "PEN_TOOL_ACTION_DISPLAY": "اضغط وحرك للرسم.",
   "PEN_TOOL_ACTION_DISPLAY": "اضغط وحرك للرسم.",
   "PIXEL_PERFECT_SETTING": "بكسل مثالي",
   "PIXEL_PERFECT_SETTING": "بكسل مثالي",
-  "RECTANGLE_TOOL_TOOLTIP": "رسم مستطيل على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم مربع.",
+  "RECTANGLE_TOOL_TOOLTIP": "رسم مستطيل على اللوحة ({0}). اضغط مع الاستمرار على Shift لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم مستطيل. اضغط مع الاستمرار على Shift لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم مستطيل. اضغط مع الاستمرار على Shift لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك لرسم مربع.",
   "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك لرسم مربع.",
   "KEEP_ORIGINAL_IMAGE_SETTING": "احتفظ بالصورة الأصلية",
   "KEEP_ORIGINAL_IMAGE_SETTING": "احتفظ بالصورة الأصلية",
-  "ROTATE_VIEWPORT_TOOLTIP": "يدور منفذ العرض. ({0})",
+  "ROTATE_VIEWPORT_TOOLTIP": "يدير إطار العرض. ({0})",
   "ROTATE_VIEWPORT_ACTION_DISPLAY": "انقر وحرك لتدوير منفذ العرض",
   "ROTATE_VIEWPORT_ACTION_DISPLAY": "انقر وحرك لتدوير منفذ العرض",
   "SELECT_TOOL_TOOLTIP": "يختار المنطقة. ({0})",
   "SELECT_TOOL_TOOLTIP": "يختار المنطقة. ({0})",
-  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد منطقة. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl لقصها منه.",
+  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد منطقة. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl لإنقاصها منه.",
   "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "انقر وانتقل للإضافة إلى التحديد الحالي.",
   "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "انقر وانتقل للإضافة إلى التحديد الحالي.",
-  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "انقر وانتقل للقص من التحديد الحالي.",
+  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "انقر وانتقل للإنقاص من التحديد الحالي.",
   "ZOOM_TOOL_TOOLTIP": "تكبير / تصغير منفذ العرض ({0}). انقر للتكبير ، او مع الاستمرار في الضغط على مفتاح alt وانقر للتصغير.",
   "ZOOM_TOOL_TOOLTIP": "تكبير / تصغير منفذ العرض ({0}). انقر للتكبير ، او مع الاستمرار في الضغط على مفتاح alt وانقر للتصغير.",
   "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك للتكبير. انقر للتكبير ، واضغط باستمرار على ctrl وانقر للتصغير.",
   "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك للتكبير. انقر للتكبير ، واضغط باستمرار على ctrl وانقر للتصغير.",
   "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك للتكبير. انقر للتصغير ، وحرر مفتاح التحكم وانقر للتكبير.",
   "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك للتكبير. انقر للتصغير ، وحرر مفتاح التحكم وانقر للتكبير.",
   "BRIGHTNESS_TOOL_TOOLTIP": "جعل وحدات البكسل أفتح أو أغمق ({0}). اضغط باستمرار على مفتاح Ctrl لجعل البكسل أغمق.",
   "BRIGHTNESS_TOOL_TOOLTIP": "جعل وحدات البكسل أفتح أو أغمق ({0}). اضغط باستمرار على مفتاح Ctrl لجعل البكسل أغمق.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "ارسم على وحدات البكسل لجعلها أكثر إشراقًا. اضغط باستمرار على Ctrl للتغميق.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "ارسم بالبكسل لجعلها أكثر قتامة. اترك Ctrl لتفتيح.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "ارسم على وحدات البكسل لجعلها أفتح. اضغط باستمرار على Ctrl للتغميق.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "ارسم بالبكسل لجعلها أغمق. اترك Ctrl لتفتيح.",
   "COLOR_PICKER_TOOLTIP": "يختار اللون الأساسي من الصورة. ({0})",
   "COLOR_PICKER_TOOLTIP": "يختار اللون الأساسي من الصورة. ({0})",
-  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "انقر لاختيار الألوان. اضغط باستمرار على Ctrl لإخفاء الصورة. اضغط مع الاستمرار على Shift لإخفاء الطبقة المرجعية",
+  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "انقر لاختيار الألوان. اضغط باستمرار على Ctrl لإخفاء اللوحة. اضغط مع الاستمرار على Shift لإخفاء الطبقة المرجعية",
   "ELLIPSE_TOOL_TOOLTIP": "رسم قطع ناقص على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_TOOLTIP": "رسم قطع ناقص على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وحرك الماوس لرسم شكل بيضاوي. اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وحرك الماوس لرسم شكل بيضاوي. اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم دائرة.",
   "ERASER_TOOL_TOOLTIP": "يمحو اللون من البكسل. ({0})",
   "ERASER_TOOL_TOOLTIP": "يمحو اللون من البكسل. ({0})",
   "ERASER_TOOL_ACTION_DISPLAY": "انقر وحرك للمسح.",
   "ERASER_TOOL_ACTION_DISPLAY": "انقر وحرك للمسح.",
   "FLOOD_FILL_TOOL_TOOLTIP": "املأ المنطقة باللون. ({0})",
   "FLOOD_FILL_TOOL_TOOLTIP": "املأ المنطقة باللون. ({0})",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "اضغط على منطقة لملئها. اضغط باستمرار على مفتاح Ctrl للتطبيق على جميع الطبقات.",
-  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "اضغط على منطقة لملئها. حرر Ctrl للنظر في الطبقات الحالية فقط.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "اضغط على منطقة لملئها. اضغط باستمرار على مفتاح Ctrl لاعتبار جميع الطبقات.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "اضغط على منطقة لملئها. حرر Ctrl لاعتبار الطبقات الحالية فقط.",
   "LASSO_TOOL_TOOLTIP": "لاسو. ({0})",
   "LASSO_TOOL_TOOLTIP": "لاسو. ({0})",
-  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد وحدات البكسل داخل لاسو. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl للحذف منه.",
+  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لتحديد وحدات البكسل داخل لاسو. اضغط مع الاستمرار على Shift للإضافة إلى التحديد الحالي. استمر في الضغط على Ctrl للإنقاص منه.",
   "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "انقر وتحرك لإضافة وحدات بكسل داخل لاسو إلى التحديد.",
   "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "انقر وتحرك لإضافة وحدات بكسل داخل لاسو إلى التحديد.",
   "LASSO_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك لحذف وحدات البكسل داخل لاسو من التحديد.",
   "LASSO_TOOL_ACTION_DISPLAY_CTRL": "انقر وتحرك لحذف وحدات البكسل داخل لاسو من التحديد.",
-  "LINE_TOOL_TOOLTIP": "رسم خط على الصورة ({0}). اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
+  "LINE_TOOL_TOOLTIP": "رسم خط على اللوحة ({0}). اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم خط. اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وتحرك لرسم خط. اضغط مع الاستمرار على Shift لتمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم خط مع تمكين الالتقاط.",
   "LINE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم خط مع تمكين الالتقاط.",
   "MAGIC_WAND_TOOL_TOOLTIP": "العصا السحرية ({0}). ملء المحدد",
   "MAGIC_WAND_TOOL_TOOLTIP": "العصا السحرية ({0}). ملء المحدد",
@@ -320,42 +313,42 @@
   "COLOR_PICKER_TOOL": "أداة انتقاء اللون",
   "COLOR_PICKER_TOOL": "أداة انتقاء اللون",
   "ELLIPSE_TOOL": "القطع الناقص",
   "ELLIPSE_TOOL": "القطع الناقص",
   "ERASER_TOOL": "ممحاة",
   "ERASER_TOOL": "ممحاة",
-  "FLOOD_FILL_TOOL": "ملء الفيضانات",
+  "FLOOD_FILL_TOOL": "ملؤ الفيضانات",
   "LASSO_TOOL": "لاسو",
   "LASSO_TOOL": "لاسو",
   "LINE_TOOL": "خط",
   "LINE_TOOL": "خط",
   "MAGIC_WAND_TOOL": "العصا السحرية",
   "MAGIC_WAND_TOOL": "العصا السحرية",
   "MOVE_TOOL": "تحريك",
   "MOVE_TOOL": "تحريك",
-  "MOVE_VIEWPORT_TOOL": "تحريك منفذ العرض",
+  "MOVE_VIEWPORT_TOOL": "تحريك إطار العرض",
   "RECTANGLE_TOOL": "مستطيل",
   "RECTANGLE_TOOL": "مستطيل",
-  "ROTATE_VIEWPORT_TOOL": "تدوير منفذ العرض",
+  "ROTATE_VIEWPORT_TOOL": "تدوير إطار العرض",
   "SELECT_TOOL_NAME": "حدد",
   "SELECT_TOOL_NAME": "حدد",
   "ZOOM_TOOL": "تكبير",
   "ZOOM_TOOL": "تكبير",
   "SHAPE_LABEL": "شكل",
   "SHAPE_LABEL": "شكل",
   "MODE_LABEL": "الوضع",
   "MODE_LABEL": "الوضع",
   "SCOPE_LABEL": "نطاق",
   "SCOPE_LABEL": "نطاق",
-  "FILL_SHAPE_LABEL": "ملء الشكل",
-  "FILL_COLOR_LABEL": "ملء اللون",
+  "FILL_SHAPE_LABEL": "ملؤ الشكل",
+  "FILL_COLOR_LABEL": "ملؤ اللون",
   "TOOL_SIZE_LABEL": "حجم الأداة",
   "TOOL_SIZE_LABEL": "حجم الأداة",
   "STRENGTH_LABEL": "قوة",
   "STRENGTH_LABEL": "قوة",
   "NEW": "جديد",
   "NEW": "جديد",
-  "ADD": "اضافة",
-  "SUBTRACT": "قص",
-  "INTERSECT": "تتقاطع",
+  "ADD": "إضافة",
+  "SUBTRACT": "إنقاص",
+  "INTERSECT": "تقاطع",
   "RECTANGLE": "مستطيل",
   "RECTANGLE": "مستطيل",
   "CIRCLE": "دائرة",
   "CIRCLE": "دائرة",
   "ABOUT": "حول البرنامج",
   "ABOUT": "حول البرنامج",
   "MINIMIZE": "تصغير",
   "MINIMIZE": "تصغير",
-  "RESTORE": "اعادة",
+  "RESTORE": "إعادة",
   "MAXIMIZE": "تكبير",
   "MAXIMIZE": "تكبير",
-  "CLOSE": "اغلق",
-  "EXPORT_SIZE_HINT": "إذا كنت ترغب في مشاركة الصورة ، فجرّب {0}٪ للحصول على أفضل وضوح",
-  "CREATE": "انشاء",
+  "CLOSE": "إغلاق",
+  "EXPORT_SIZE_HINT": "إن كنت ترغب في مشاركة الصورة ، فجرّب {0}٪ للحصول على أفضل وضوح",
+  "CREATE": "إنشاء",
   "BASE_LAYER_NAME": "الطبقة الرئيسية",
   "BASE_LAYER_NAME": "الطبقة الرئيسية",
   "ENABLE_MASK": "تمكين القناع",
   "ENABLE_MASK": "تمكين القناع",
   "SELECTED_AREA_EMPTY": "المنطقة المحددة فارغة",
   "SELECTED_AREA_EMPTY": "المنطقة المحددة فارغة",
   "NOTHING_TO_COPY": "لا يوجد شي لنسخه",
   "NOTHING_TO_COPY": "لا يوجد شي لنسخه",
   "REFERENCE_LAYER_PATH": "مسار الطبقة المرجعية",
   "REFERENCE_LAYER_PATH": "مسار الطبقة المرجعية",
-  "FLIP": "توجيه",
+  "FLIP": "قلب",
   "ROTATION": "دوران",
   "ROTATION": "دوران",
   "ROT_IMG_90_D": "تدوير الصورة 90 درجة",
   "ROT_IMG_90_D": "تدوير الصورة 90 درجة",
   "ROT_IMG_180_D": "تدوير الصورة 180 درجة",
   "ROT_IMG_180_D": "تدوير الصورة 180 درجة",
@@ -366,63 +359,63 @@
   "UNNAMED_PALETTE": "لوح غير مسمى",
   "UNNAMED_PALETTE": "لوح غير مسمى",
   "CLICK_SELECT_PRIMARY": "انقر لتحديد اللون الرئيسي.",
   "CLICK_SELECT_PRIMARY": "انقر لتحديد اللون الرئيسي.",
   "PEN_MODE": "وضع القلم",
   "PEN_MODE": "وضع القلم",
-  "VIEW": "منظر",
+  "VIEW": "إظهار",
   "HORIZONTAL_LINE_SYMMETRY": "تناظر الخط الأفقي",
   "HORIZONTAL_LINE_SYMMETRY": "تناظر الخط الأفقي",
   "VERTICAL_LINE_SYMMETRY": "تناظر الخط العمودي",
   "VERTICAL_LINE_SYMMETRY": "تناظر الخط العمودي",
   "COLOR_PICKER_TITLE": "أداة اختيار اللون",
   "COLOR_PICKER_TITLE": "أداة اختيار اللون",
-  "COLOR_SLIDERS_TITLE": "لوحة الالوان",
+  "COLOR_SLIDERS_TITLE": "أشرطة الألوان",
   "PALETTE_TITLE": "اللوحة",
   "PALETTE_TITLE": "اللوحة",
   "SWATCHES_TITLE": "حوامل",
   "SWATCHES_TITLE": "حوامل",
   "LAYERS_TITLE": "الطبقات",
   "LAYERS_TITLE": "الطبقات",
   "NORMAL_BLEND_MODE": "عادي",
   "NORMAL_BLEND_MODE": "عادي",
-  "DARKEN_BLEND_MODE": "أغمق",
+  "DARKEN_BLEND_MODE": "تغميق",
   "MULTIPLY_BLEND_MODE": "تضاعف",
   "MULTIPLY_BLEND_MODE": "تضاعف",
   "COLOR_BURN_BLEND_MODE": "احتراق الالوان",
   "COLOR_BURN_BLEND_MODE": "احتراق الالوان",
-  "LIGHTEN_BLEND_MODE": "فاتح",
+  "LIGHTEN_BLEND_MODE": "تفتيح",
   "SCREEN_BLEND_MODE": "شاشة",
   "SCREEN_BLEND_MODE": "شاشة",
   "COLOR_DODGE_BLEND_MODE": "انقاص كثافة اللون",
   "COLOR_DODGE_BLEND_MODE": "انقاص كثافة اللون",
   "OVERLAY_BLEND_MODE": "تراكب",
   "OVERLAY_BLEND_MODE": "تراكب",
   "SOFT_LIGHT_BLEND_MODE": "ضوء خافت",
   "SOFT_LIGHT_BLEND_MODE": "ضوء خافت",
-  "HARD_LIGHT_BLEND_MODE": "ضوء غامق",
-  "DIFFERENCE_BLEND_MODE": "اختلاف",
+  "HARD_LIGHT_BLEND_MODE": "ضوء شديد",
+  "DIFFERENCE_BLEND_MODE": "الفرق",
   "EXCLUSION_BLEND_MODE": "استبعاد",
   "EXCLUSION_BLEND_MODE": "استبعاد",
-  "HUE_BLEND_MODE": "مسحة",
+  "HUE_BLEND_MODE": "المسحة",
   "SATURATION_BLEND_MODE": "التشبع",
   "SATURATION_BLEND_MODE": "التشبع",
-  "LUMINOSITY_BLEND_MODE": "لمعان",
+  "LUMINOSITY_BLEND_MODE": "اللمعان",
   "COLOR_BLEND_MODE": "اللون",
   "COLOR_BLEND_MODE": "اللون",
   "NOT_SUPPORTED_BLEND_MODE": "غير مدعوم",
   "NOT_SUPPORTED_BLEND_MODE": "غير مدعوم",
-  "RESTART": "اعد تشغيل",
+  "RESTART": "إعادة التشغيل",
   "SORT_BY": "ترتيب حسب",
   "SORT_BY": "ترتيب حسب",
   "NAME": "الاسم",
   "NAME": "الاسم",
-  "COLORS": "الالوان",
+  "COLORS": "الألوان",
   "DEFAULT": "الافتراضي",
   "DEFAULT": "الافتراضي",
-  "ALPHABETICAL": "مرتب حسب الحروف الأبجدية",
+  "ALPHABETICAL": "أبجدي",
   "COLOR_COUNT": "عدد الألوان",
   "COLOR_COUNT": "عدد الألوان",
-  "ANY": "اي",
+  "ANY": "أي",
   "MAX": "الاقصى",
   "MAX": "الاقصى",
-  "MIN": "الاقل",
+  "MIN": "الأدنى",
   "EXACT": "بالضبط",
   "EXACT": "بالضبط",
   "ASCENDING": "تصاعدي",
   "ASCENDING": "تصاعدي",
   "DESCENDING": "تنازلي",
   "DESCENDING": "تنازلي",
-  "NAME_IS_TOO_LONG": "الاسم طويل جدا",
+  "NAME_IS_TOO_LONG": "الاسم طويل جدًا",
   "STOP_IT_TEXT1": "هذا يكفي. رتب أسماء ملفاتك.",
   "STOP_IT_TEXT1": "هذا يكفي. رتب أسماء ملفاتك.",
   "STOP_IT_TEXT2": "هل يمكنك التوقف عن نسخ هذه الأسماء من فضلك؟",
   "STOP_IT_TEXT2": "هل يمكنك التوقف عن نسخ هذه الأسماء من فضلك؟",
-  "REPLACER_TOOLTIP": "انقر بزر الماوس الأيمن على لوحة الألوان واختر 'استبدال' أو أسقطها هنا.",
+  "REPLACER_TOOLTIP": "انقر زر الماوس الأيمن على لوحة الألوان واختر 'استبدال' أو ألقها هنا.",
   "CLICK_TO_CHOOSE_COLOR": "انقر لاختيار اللون",
   "CLICK_TO_CHOOSE_COLOR": "انقر لاختيار اللون",
   "REPLACE_COLOR": "استبدل اللون",
   "REPLACE_COLOR": "استبدل اللون",
-  "PALETTE_COLOR_TOOLTIP": "انقر لتحديد اللون الرئيسي. قم بالسحب والإفلات على لون لوح آخر لتبديلها.",
+  "PALETTE_COLOR_TOOLTIP": "انقر لتحديده كلون رئيسي. قم بالسحب والإفلات على لون لوح آخر لتبديلها.",
   "ADD_FROM_SWATCHES": "أضف من العينات",
   "ADD_FROM_SWATCHES": "أضف من العينات",
   "ADD_COLOR_TO_PALETTE": "أضف اللون إلى لوح الألوان",
   "ADD_COLOR_TO_PALETTE": "أضف اللون إلى لوح الألوان",
   "USE_IN_CURRENT_IMAGE": "استخدم في الصورة الحالية",
   "USE_IN_CURRENT_IMAGE": "استخدم في الصورة الحالية",
-  "ADD_TO_FAVORITES": "اضافة الى المفضلة",
+  "ADD_TO_FAVORITES": "إضافة الى المفضلة",
   "BROWSE_PALETTES": "تصفح اللوحات",
   "BROWSE_PALETTES": "تصفح اللوحات",
   "LOAD_PALETTE": "تحميل لوحة",
   "LOAD_PALETTE": "تحميل لوحة",
-  "SAVE_PALETTE": "حفض اللوحة",
+  "SAVE_PALETTE": "حفظ اللوحة",
   "FAVORITES": "المفضلة",
   "FAVORITES": "المفضلة",
   "ADD_FROM_CURRENT_PALETTE": "أضف من اللوحة الحالية",
   "ADD_FROM_CURRENT_PALETTE": "أضف من اللوحة الحالية",
   "OPEN_PALETTES_DIR_TOOLTIP": "افتح دليل اللوحات في المستكشف",
   "OPEN_PALETTES_DIR_TOOLTIP": "افتح دليل اللوحات في المستكشف",
   "BROWSE_ON_LOSPEC_TOOLTIP": "تصفح اللوحات على Lospec",
   "BROWSE_ON_LOSPEC_TOOLTIP": "تصفح اللوحات على Lospec",
-  "IMPORT_FROM_FILE_TOOLTIP": "اضافة من ملف",
+  "IMPORT_FROM_FILE_TOOLTIP": "استيراد من ملف",
   "TOP_LEFT": "أعلى اليسار",
   "TOP_LEFT": "أعلى اليسار",
   "TOP_CENTER": "اعلى الوسط",
   "TOP_CENTER": "اعلى الوسط",
   "TOP_RIGHT": "اعلى اليمين",
   "TOP_RIGHT": "اعلى اليمين",
@@ -433,29 +426,28 @@
   "BOTTOM_CENTER": "اسفل الوسط",
   "BOTTOM_CENTER": "اسفل الوسط",
   "BOTTOM_RIGHT": "أسفل اليمين",
   "BOTTOM_RIGHT": "أسفل اليمين",
   "CLIP_TO_BELOW": "مقطع للطبقة أدناه",
   "CLIP_TO_BELOW": "مقطع للطبقة أدناه",
-  "MOVE_UPWARDS": "تحرك لأعلى",
+  "MOVE_UPWARDS": "تحريك لأعلى",
   "MOVE_DOWNWARDS": "تحريك للاسفل",
   "MOVE_DOWNWARDS": "تحريك للاسفل",
   "MERGE_SELECTED": "دمج المحدد",
   "MERGE_SELECTED": "دمج المحدد",
   "LOCK_TRANSPARENCY": "قفل الشفافية",
   "LOCK_TRANSPARENCY": "قفل الشفافية",
   "COULD_NOT_LOAD_PALETTE": "تعذر إحضار اللوحات",
   "COULD_NOT_LOAD_PALETTE": "تعذر إحضار اللوحات",
   "NO_PALETTES_FOUND": "لم يتم العثور على لوحات.",
   "NO_PALETTES_FOUND": "لم يتم العثور على لوحات.",
   "LOSPEC_LINK_TEXT": "سمعت أنه يمكنك العثور على بعضها هنا: lospec.com/palette-list",
   "LOSPEC_LINK_TEXT": "سمعت أنه يمكنك العثور على بعضها هنا: lospec.com/palette-list",
-  "PALETTE_BROWSER": "متصفح لوح الألوان",
-  "DELETE_PALETTE_CONFIRMATION": "هل أنت متأكد أنك تريد حذف هذه اللوحة؟ هذا لا يمكن التراجع عنها.",
-  "SHORTCUTS_IMPORTED": "تم اضافة الاختصارات من {0} بنجاح.",
-  "SHORTCUT_PROVIDER_DETECTED": "لقد اكتشفنا أنك قمت بتثبيت {0}. هل تريد اضافة الاختصارات منه؟",
-  "IMPORT_FROM_INSTALLATION": "اضافة من التثبيت",
-  "IMPORT_INSTALLATION_OPTION1": "اضافة من التنزيلات",
-  "IMPORT_INSTALLATION_OPTION2": "استخدام الافتراضي",
-  "IMPORT_FROM_TEMPLATE": "اضافة من قالب",
+  "PALETTE_BROWSER": "متصفح ألواح الألوان",
+  "DELETE_PALETTE_CONFIRMATION": "هل أنت متأكد أنك تريد حذف هذه اللوحة؟ لا يمكن التراجع عن هذا الفعل.",
+  "SHORTCUTS_IMPORTED": "تم استيراد الاختصارات من {0} بنجاح.",
+  "SHORTCUT_PROVIDER_DETECTED": "لقد وجدنا أنك قمت بتثبيت {0}. هل تريد إضافة الاختصارات منها؟",
+  "IMPORT_INSTALLATION_OPTION1": "إضافة من التثبيت",
+  "IMPORT_INSTALLATION_OPTION2": "استخدام الافتراضيات",
+  "IMPORT_FROM_TEMPLATE": "استيراد من قالب",
   "SHORTCUTS_IMPORTED_SUCCESS": "تم اضافة الاختصارات بنجاح.",
   "SHORTCUTS_IMPORTED_SUCCESS": "تم اضافة الاختصارات بنجاح.",
-  "WARNING_RESET_SHORTCUTS_DEFAULT": "هل أنت متأكد من أنك تريد إعادة تعيين جميع الاختصارات إلى قيمتها الافتراضية؟",
+  "WARNING_RESET_SHORTCUTS_DEFAULT": "هل أنت متأكد من أنك تريد إعادة تعيين جميع الاختصارات إلى قيمها الافتراضية؟",
   "SUCCESS": "نجاح",
   "SUCCESS": "نجاح",
   "WARNING": "تحذير",
   "WARNING": "تحذير",
   "ERROR_IMPORTING_IMAGE": "حدث خطأ أثناء استيراد الصورة.",
   "ERROR_IMPORTING_IMAGE": "حدث خطأ أثناء استيراد الصورة.",
   "SHORTCUTS_CORRUPTED_TITLE": "ملف الاختصارات تالف",
   "SHORTCUTS_CORRUPTED_TITLE": "ملف الاختصارات تالف",
-  "SHORTCUTS_CORRUPTED": "تعرض ملف الاختصارات للتلف ، اعادة التعيين إلى الوضع الافتراضي.",
-  "FAILED_DOWNLOAD_PALETTE": "فشل تحميل لوح الألوان",
+  "SHORTCUTS_CORRUPTED": "كان ملف الاختصارات مصابًا بالتلف، اعادة التعيين إلى الوضع الافتراضي.",
+  "FAILED_DOWNLOAD_PALETTE": "فشل تنزيل لوح الألوان",
   "FILE_INCORRECT_FORMAT": "لم يكن الملف بتنسيق صحيح",
   "FILE_INCORRECT_FORMAT": "لم يكن الملف بتنسيق صحيح",
   "INVALID_FILE": "ملف غير صالح",
   "INVALID_FILE": "ملف غير صالح",
   "SHORTCUTS_FILE_INCORRECT_FORMAT": "تنسيق ملف الاختصارات ليس صحيحًا",
   "SHORTCUTS_FILE_INCORRECT_FORMAT": "تنسيق ملف الاختصارات ليس صحيحًا",
@@ -465,31 +457,29 @@
   "SWAP": "تبديل",
   "SWAP": "تبديل",
   "SHORTCUT_ALREADY_ASSIGNED_SWAP": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الحالي أم تبديل الاختصارين؟",
   "SHORTCUT_ALREADY_ASSIGNED_SWAP": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الحالي أم تبديل الاختصارين؟",
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الموجود؟",
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "تم بالفعل تعيين هذا الاختصار لـ '{0}'\nهل تريد استبدال الاختصار الموجود؟",
-  "UNSAVED_CHANGES": "تغييرات غير محفوضة",
-  "DOCUMENT_MODIFIED_SAVE": "تم تعديل العنصر. هل تريد ان تحفظ التغييرات؟",
-  "SESSION_UNSAVED_DATA": "{0} ببيانات غير محفوظة. هل أنت متأكد؟",
+  "UNSAVED_CHANGES": "تغييرات غير محفوظة",
+  "DOCUMENT_MODIFIED_SAVE": "تم تعديل المستند. هل تريد ان تحفظ التغييرات؟",
   "PROJECT_MAINTAINERS": "مشرفو المشروع",
   "PROJECT_MAINTAINERS": "مشرفو المشروع",
   "OTHER_AWESOME_CONTRIBUTORS": "وغيرهم من المساهمين الرائعين",
   "OTHER_AWESOME_CONTRIBUTORS": "وغيرهم من المساهمين الرائعين",
-  "HELP": "مساعد",
-  "STOP_IT_TEXT3": "لا ، حقًا ، توقف عن ذلك.",
-  "STOP_IT_TEXT4": "أليس لديك أي شيء أفضل لتفعله؟",
+  "HELP": "مساعدة",
+  "STOP_IT_TEXT3": "لا، حقًا ، توقف عن ذلك.",
+  "STOP_IT_TEXT4": "أليس لديك شيء أفضل تفعله؟",
   "LINEAR_DODGE_BLEND_MODE": "مراوغة خطية (إضافة)",
   "LINEAR_DODGE_BLEND_MODE": "مراوغة خطية (إضافة)",
   "PRESS_ANY_KEY": "اضغط اي مفتاح",
   "PRESS_ANY_KEY": "اضغط اي مفتاح",
   "NONE_SHORTCUT": "غير محدد",
   "NONE_SHORTCUT": "غير محدد",
   "REFERENCE": "مرجع",
   "REFERENCE": "مرجع",
   "PUT_REFERENCE_LAYER_ABOVE": "ضع طبقة مرجعية أعلاه",
   "PUT_REFERENCE_LAYER_ABOVE": "ضع طبقة مرجعية أعلاه",
   "PUT_REFERENCE_LAYER_BELOW": "ضع طبقة مرجعية أدناه",
   "PUT_REFERENCE_LAYER_BELOW": "ضع طبقة مرجعية أدناه",
-  "TOGGLE_VERTICAL_SYMMETRY": "تبديل التناظر العمودي",
-  "TOGGLE_HORIZONTAL_SYMMETRY": "تبديل التناظر الأفقي",
-  "RESET_VIEWPORT": "إعادة تعيين منفذ العرض",
-  "VIEWPORT_SETTINGS": "إعدادات منفذ العرض",
+  "TOGGLE_VERTICAL_SYMMETRY": "تفعيل/إبطال التناظر العمودي",
+  "TOGGLE_HORIZONTAL_SYMMETRY": "تفعيل/إبطال التناظر الأفقي",
+  "RESET_VIEWPORT": "إعادة تعيين إطار العرض",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "انقر بالماوس مع الاستمرار لتحريك وحدات البكسل في الطبقات المحددة.",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "انقر بالماوس مع الاستمرار لتحريك وحدات البكسل في الطبقات المحددة.",
   "CTRL_KEY": "Ctrl",
   "CTRL_KEY": "Ctrl",
   "SHIFT_KEY": "Shift",
   "SHIFT_KEY": "Shift",
   "ALT_KEY": "Alt",
   "ALT_KEY": "Alt",
   "RENAME": "إعادة تسمية",
   "RENAME": "إعادة تسمية",
   "PIXEL_UNIT": "بكسل",
   "PIXEL_UNIT": "بكسل",
-  "OPEN_LOCALIZATION_DEBUG_WINDOW": "افتح نافذة تصحيح أخطاء الترجمة",
+  "OPEN_LOCALIZATION_DEBUG_WINDOW": "افتح نافذة معالجة أخطاء الترجمة",
   "FORCE_OTHER_FLOW_DIRECTION": "فرض اتجاه تدفق آخر",
   "FORCE_OTHER_FLOW_DIRECTION": "فرض اتجاه تدفق آخر",
   "API_KEY": "مفتاح API",
   "API_KEY": "مفتاح API",
   "LOCALIZATION_VIEW_TYPE": "نوع عرض الترجمة",
   "LOCALIZATION_VIEW_TYPE": "نوع عرض الترجمة",
@@ -507,155 +497,612 @@
   "SECURITY_ERROR_MSG": "لا توجد حقوق للكتابة إلى الموقع المحدد.",
   "SECURITY_ERROR_MSG": "لا توجد حقوق للكتابة إلى الموقع المحدد.",
   "IO_ERROR": "خطأ IO",
   "IO_ERROR": "خطأ IO",
   "IO_ERROR_MSG": "خطأ أثناء الكتابة على القرص.",
   "IO_ERROR_MSG": "خطأ أثناء الكتابة على القرص.",
-  "FAILED_ASSOCIATE_PIXI": "فشل إقران ملف .pixi بـ PixiEditor.",
   "COULD_NOT_SAVE_PALETTE": "حدث خطأ أثناء حفظ اللوحة.",
   "COULD_NOT_SAVE_PALETTE": "حدث خطأ أثناء حفظ اللوحة.",
   "NO_COLORS_TO_SAVE": "لا توجد ألوان للحفظ.",
   "NO_COLORS_TO_SAVE": "لا توجد ألوان للحفظ.",
   "SINGLE_LAYER": "طبقة واحدة",
   "SINGLE_LAYER": "طبقة واحدة",
   "CHOOSE": "اختيار",
   "CHOOSE": "اختيار",
-  "REMOVE": "ازالة",
+  "REMOVE": "إزالة",
   "FILE_FORMAT_NOT_ASEPRITE_KEYS": "الملف ليس ملف \".aseprite-keys\"",
   "FILE_FORMAT_NOT_ASEPRITE_KEYS": "الملف ليس ملف \".aseprite-keys\"",
   "FILE_HAS_INVALID_SHORTCUT": "يحتوي الملف على اختصار غير صالح",
   "FILE_HAS_INVALID_SHORTCUT": "يحتوي الملف على اختصار غير صالح",
   "FILE_EXTENSION_NOT_SUPPORTED": "نوع الملف '{0}' غير مدعوم",
   "FILE_EXTENSION_NOT_SUPPORTED": "نوع الملف '{0}' غير مدعوم",
   "ERROR_READING_FILE": "خطأ أثناء قراءة الملف",
   "ERROR_READING_FILE": "خطأ أثناء قراءة الملف",
   "DISCARD_PALETTE": "تجاهل لوح الألوان",
   "DISCARD_PALETTE": "تجاهل لوح الألوان",
-  "DISCARD_PALETTE_CONFIRMATION": "هل أنت متأكد أنك تريد تجاهل لوحة الألوان الحالية؟ هذا لا يمكن التراجع عنها.",
-  "IMPORT_AS_NEW_LAYER": "اضافة كطبقة جديدة",
+  "DISCARD_PALETTE_CONFIRMATION": "هل أنت متأكد أنك تريد تجاهل لوح الألوان الحالي؟ هذا لا يمكن التراجع عنه.",
+  "IMPORT_AS_NEW_LAYER": "استيراد كطبقة جديدة",
   "PASTE_AS_PRIMARY_COLOR": "لصق كلون أساسي",
   "PASTE_AS_PRIMARY_COLOR": "لصق كلون أساسي",
-  "IMPORT_AS_NEW_FILE": "اضافة كملف جديد",
-  "IMPORT_PALETTE_FILE": "اضافة ملف لوح الألوان",
-  "IMPORT_MULTIPLE_PALETTE_COLORS": "اضافة الألوان إلى لوح الألوان",
-  "IMPORT_SINGLE_PALETTE_COLOR": "اضافة اللون إلى لوح الألوان",
-  "IMPORT_AS_REFERENCE_LAYER": "اضافة كطبقة مرجعية",
+  "IMPORT_AS_NEW_FILE": "استيراد كملف جديد",
+  "IMPORT_PALETTE_FILE": "إضافة ملف لوح الألوان",
+  "IMPORT_MULTIPLE_PALETTE_COLORS": "استيراد الألوان إلى لوح الألوان",
+  "IMPORT_SINGLE_PALETTE_COLOR": "استيراد اللون إلى لوح الألوان",
+  "IMPORT_AS_REFERENCE_LAYER": "استيراد كطبقة مرجعية",
   "NAVIGATOR_PICK_ACTION_DISPLAY": "انقر بزر الماوس الأيمن لاختيار اللون ، وانقر بزر الماوس الأيمن مع الاستمرار بالضغط على Shift لنسخ اللون إلى الحافظة",
   "NAVIGATOR_PICK_ACTION_DISPLAY": "انقر بزر الماوس الأيمن لاختيار اللون ، وانقر بزر الماوس الأيمن مع الاستمرار بالضغط على Shift لنسخ اللون إلى الحافظة",
   "OPEN_FILE_FROM_CLIPBOARD": "فتح من الحافظة",
   "OPEN_FILE_FROM_CLIPBOARD": "فتح من الحافظة",
   "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "فتح من الحافظة",
   "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "فتح من الحافظة",
   "OPEN_LOCALIZATION_DATA": "هل تريد فتح LocalizationData.json؟\nتم وضع البيانات المحدثة في الحافظة.\nلاحظ أنه لن يتم تطبيق التغييرات حتى إعادة تشغيل البرنامج",
   "OPEN_LOCALIZATION_DATA": "هل تريد فتح LocalizationData.json؟\nتم وضع البيانات المحدثة في الحافظة.\nلاحظ أنه لن يتم تطبيق التغييرات حتى إعادة تشغيل البرنامج",
   "DOWNLOADING_LANGUAGE_FAILED": "فشل تحميل اللغة.\nربما تم الإفراط في استخدام مفتاح API.",
   "DOWNLOADING_LANGUAGE_FAILED": "فشل تحميل اللغة.\nربما تم الإفراط في استخدام مفتاح API.",
-  "LOCALIZATION_DATA_NOT_FOUND": "مسار بيانات الترجمة غير موجود",
-  "APPLY": "حفض",
+  "LOCALIZATION_DATA_NOT_FOUND": "لم يتم العثور على مسار بيانات الترجمة",
+  "APPLY": "تطبيق",
   "UPDATE_SOURCE": "تحديث المصدر",
   "UPDATE_SOURCE": "تحديث المصدر",
-  "COPY_TO_CLIPBOARD": "نسخ للحافضة",
-  "LANGUAGE_FILE_NOT_FOUND": "ملف اللغة غير موجود.\nالبحث عن {0}",
-  "PROJECT_ROOT_NOT_FOUND": "لم يتم العثور على جذر مشروع PixiEditor.\nأبحث عن PixiEditor.csproj",
-  "LOCALIZATION_FOLDER_NOT_FOUND": "مجلد الترجمة غير موجود.\nأبحث عن/البيانات/التعريب",
-  "SELECT_A_LANGUAGE": "اختار لغة",
-  "DONE": "انتهاء",
+  "COPY_TO_CLIPBOARD": "نسخ إلى الحافظة",
+  "LANGUAGE_FILE_NOT_FOUND": "لم يتم العثور على ملف اللغة.\nالبحث عن {0}",
+  "PROJECT_ROOT_NOT_FOUND": "لم يتم العثور على جذر مشروع PixiEditor.\nالبحث عن PixiEditor.csproj",
+  "LOCALIZATION_FOLDER_NOT_FOUND": "مجلد الترجمة غير موجود.\nالبحث عن/Data/Localization",
+  "SELECT_A_LANGUAGE": "اختر لغة",
+  "DONE": "تم",
   "SOURCE_UNSET_OR_MISSING": "المصدر مفقود/غير محدد",
   "SOURCE_UNSET_OR_MISSING": "المصدر مفقود/غير محدد",
-  "SOURCE_NEWER": "مصدر أحدث",
+  "SOURCE_NEWER": "المصدر أحدث",
   "SOURCE_UP_TO_DATE": "تم تحديث المصدر",
   "SOURCE_UP_TO_DATE": "تم تحديث المصدر",
-  "SOURCE_OLDER": "سحابة احدث",
+  "SOURCE_OLDER": "السحابة احدث",
   "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "انقر لاختيار الوان من الطبقة المرجعية.",
   "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "انقر لاختيار الوان من الطبقة المرجعية.",
   "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "انقر لاختيار الوان من الصورة.",
   "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "انقر لاختيار الوان من الصورة.",
-  "LOCALIZATION_DEBUG_WINDOW_TITLE": "نافذة تصحيح أخطاء الترجمة",
-  "COMMAND_DEBUG_WINDOW_TITLE": "نافذة تصحيح الأوامر",
+  "LOCALIZATION_DEBUG_WINDOW_TITLE": "نافذة معالجة أخطاء الترجمة",
   "SHORTCUTS_TITLE": "الاختصارات",
   "SHORTCUTS_TITLE": "الاختصارات",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "اسحب المقابض لتوسيع نطاق التحويل. استمر في الضغط على Ctrl واسحب المقبض لتحريك المقبض بحرية. اضغط على مفتاح Shift لتغيير الحجم بشكل متناسب. استمر في الضغط على Alt واسحب المقبض الجانبي للقص. اسحب المقابض الخارجية للتدوير.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "اسحب المقابض لتوسيع نطاق التحويل. اضغط على مفتاح Shift لتغيير الحجم بشكل متناسب. استمر في الضغط على Alt واسحب المقبض الجانبي للقص. اسحب المقابض الخارجية للتدوير.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "اسحب المقابض لتوسيع نطاق التحويل. اضغط على مفتاح Shift لتغيير الحجم بشكل متناسب. اسحب المقابض الخارجية للتدوير.",
-  "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "اسحب المقابض لتوسيع نطاق التحويل. اضغط على مفتاح Shift لتغيير الحجم بشكل متناسب.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "اسحب المقابض لتغيير الحجم. استمر في الضغط على Ctrl واسحب المقبض لتحريك المقبض بحرية. اضغط على مفتاح Shift لتغيير الحجم تناسبيًا. استمر في الضغط على Alt واسحب المقبض الجانبي للإمالة. اسحب المقابض الخارجية للتدوير.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "اسحب المقابض لتغيير الحجم. استمر في الضغط على Ctrl واسحب المقبض لتحريك المقبض بحرية. اضغط على مفتاح Shift لتغيير الحجم تناسبيًا. استمر في الضغط على Alt واسحب المقبض الجانبي للإمالة. اسحب المقابض الخارجية للتدوير.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "اسحب المقابض لتغيير الحجم. استمر في الضغط على Ctrl واسحب المقبض لتحريك المقبض بحرية. اضغط على مفتاح Shift لتغيير الحجم تناسبيًا. اسحب المقابض الخارجية للتدوير.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "اسحب المقابض لتوسيع نطاق التحويل. استمر في الضغط على Ctrl واسحب المقبض لتحريك المقبض بحرية. اضغط على مفتاح Shift لتغيير الحجم تناسبيًا.",
   "OPEN_DOCUMENTATION": "فتح الوثائق",
   "OPEN_DOCUMENTATION": "فتح الوثائق",
   "LOCAL_PALETTE_SOURCE_NAME": "محلي",
   "LOCAL_PALETTE_SOURCE_NAME": "محلي",
   "ERROR_FORBIDDEN_UNIQUE_NAME": "لا يمكن أن يبدأ الاسم الفريد للملحق بـ 'pixieditor'.",
   "ERROR_FORBIDDEN_UNIQUE_NAME": "لا يمكن أن يبدأ الاسم الفريد للملحق بـ 'pixieditor'.",
-  "ERROR_MISSING_METADATA": "مفتاح البيانات الوصفية للإضافة '{0}' مفقود.",
-  "ERROR_NO_CLASS_ENTRY": "إدخال فئة الامتداد مفقود في المسار '{0}'.",
-  "ERROR_NO_ENTRY_ASSEMBLY": "تجميع إدخال الملحق مفقود في المسار '{0}'.",
-  "ERROR_MISSING_ADDITIONAL_CONTENT": "إعدادك الحالي لا يسمح بتحميل هذا الملحق. ربما لا تملكه أو لم تقم بتثبيته. يمكنك شرائه هنا '{0}'.",
+  "ERROR_MISSING_METADATA": "مفتاح البيانات الوصفية للملحق '{0}' مفقود.",
+  "ERROR_NO_CLASS_ENTRY": "مُدخل فئة الملحق مفقود في المسار '{0}'.",
+  "ERROR_NO_ENTRY_ASSEMBLY": "تركيب مُدخل الملحق مفقود في المسار '{0}'.",
+  "ERROR_MISSING_ADDITIONAL_CONTENT": "إعدادك الحالي لا يسمح بتحميل هذا الملحق. ربما لا تملكه أو لم تقم بتثبيته. يمكنك شراؤه هنا '{0}'.",
   "BUY_SUPPORTER_PACK": "شراء حزمة الداعم",
   "BUY_SUPPORTER_PACK": "شراء حزمة الداعم",
   "NEWS": "الأخبار",
   "NEWS": "الأخبار",
   "DISABLE_NEWS_PANEL": "تعطيل لوحة الأخبار في نافذة بدء التشغيل",
   "DISABLE_NEWS_PANEL": "تعطيل لوحة الأخبار في نافذة بدء التشغيل",
-  "FAILED_FETCH_NEWS": "فشل في جمع الأخبار",
-  "CROP_TO_SELECTION": "اقتصاص للتحديد",
-  "CROP_TO_SELECTION_DESCRIPTIVE": "اقتصاص الصورة للتحديد",
+  "FAILED_FETCH_NEWS": "فشل في جلب الأخبار",
+  "CROP_TO_SELECTION": "الاقتصاص إلى التحديد",
+  "CROP_TO_SELECTION_DESCRIPTIVE": "اقتصاص الصورة إلى التحديد",
   "SHOW_CONTEXT_MENU": "عرض القائمة المنبثقة",
   "SHOW_CONTEXT_MENU": "عرض القائمة المنبثقة",
   "ERASE": "محو",
   "ERASE": "محو",
   "USE_SECONDARY_COLOR": "استخدم اللون الثانوي",
   "USE_SECONDARY_COLOR": "استخدم اللون الثانوي",
   "RIGHT_CLICK_MODE": "وضع النقر بزر الماوس الأيمن",
   "RIGHT_CLICK_MODE": "وضع النقر بزر الماوس الأيمن",
-  "ADD_PRIMARY_COLOR_TO_PALETTE": "أضف اللون الأساسي إلى لوح الألوان",
-  "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "أضف اللون الأساسي إلى اللوحة الحالية",
-  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "لا يمكن استيراد الكل",
-  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "لا يمكن استرداد الملفات بشكل كامل.\nإذا قمت بإرسال التقرير إلى المطورين\nقد يكونوا قادرين على مساعدتك.",
-  "EXPORT_SAVE_TITLE": "حدد مكان لحفض الصورة",
+  "ADD_PRIMARY_COLOR_TO_PALETTE": "إضافة اللون الأساسي إلى لوح الألوان",
+  "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "إضافة اللون الأساسي إلى اللوح الحالي",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "لم يمكن استعادة المستندات بشكل كامل",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "لم يمكن استعادة المستندات بشكل كامل. تعلّم تحفظ شغلك يا معلم.",
+  "EXPORT_SAVE_TITLE": "حدد مكانًا لحفظ الصورة",
   "BROWSE_DIRECTORY": "تصفح",
   "BROWSE_DIRECTORY": "تصفح",
   "SEND": "ارسال تقرير",
   "SEND": "ارسال تقرير",
   "OPEN_DOCKABLE_MENU": "فتح صفحة",
   "OPEN_DOCKABLE_MENU": "فتح صفحة",
   "TIMELINE_TITLE": "الجدول الزمني",
   "TIMELINE_TITLE": "الجدول الزمني",
   "EXPORT_IMAGE_HEADER": "صورة",
   "EXPORT_IMAGE_HEADER": "صورة",
-  "EXPORT_ANIMATION_HEADER": "الرسوم المتحركة",
+  "EXPORT_ANIMATION_HEADER": "رسوم متحركة",
   "PIXI_FILE": "ملفات PixiEditor",
   "PIXI_FILE": "ملفات PixiEditor",
   "PNG_FILE": "صور PNG",
   "PNG_FILE": "صور PNG",
   "JPEG_FILE": "صور JPEG",
   "JPEG_FILE": "صور JPEG",
   "GIF_FILE": "GIFs",
   "GIF_FILE": "GIFs",
   "BMP_FILE": "صور BMP",
   "BMP_FILE": "صور BMP",
-  "IMAGE_FILES": "ملفات الصور",
-  "VIDEO_FILES": "ملفات الفيديو",
+  "IMAGE_FILES": "ملفات صور",
+  "VIDEO_FILES": "ملفات فيديو",
   "MP4_FILE": "فيديوهات MP4",
   "MP4_FILE": "فيديوهات MP4",
   "COLUMNS": "الاعمدة",
   "COLUMNS": "الاعمدة",
   "ROWS": "الصفوف",
   "ROWS": "الصفوف",
   "BACKGROUND": "الخلفية",
   "BACKGROUND": "الخلفية",
-  "OPACITY": "الشفافية",
+  "OPACITY": "المرئية",
   "IS_VISIBLE": "مرئي",
   "IS_VISIBLE": "مرئي",
   "BLEND_MODE": "وضع المزج",
   "BLEND_MODE": "وضع المزج",
   "MASK": "قناع",
   "MASK": "قناع",
   "MASK_IS_VISIBLE": "القناع مرئي",
   "MASK_IS_VISIBLE": "القناع مرئي",
   "OUTPUT": "مخرجات",
   "OUTPUT": "مخرجات",
   "INPUT": "مدخلات",
   "INPUT": "مدخلات",
-  "NODE_GRAPH_TITLE": "عرض الرسم البياني",
+  "NODE_GRAPH_TITLE": "عرض بياني",
   "CONTENT": "المحتوى",
   "CONTENT": "المحتوى",
-  "RADIUS": "القطر",
-  "FILL_COLOR": "ملء اللون",
-  "TOP": "اعلى",
-  "BOTTOM": "اسفل",
+  "RADIUS": "نصف القطر",
+  "FILL_COLOR": "لون التعبئة",
+  "TOP": "أعلى",
+  "BOTTOM": "أسفل",
   "CHANNELS_DOCK_TITLE": "القنوات",
   "CHANNELS_DOCK_TITLE": "القنوات",
-  "RED": "احمر",
-  "GREEN": "اخضر",
-  "BLUE": "ازرق",
+  "RED": "أحمر",
+  "GREEN": "أخضر",
+  "BLUE": "أزرق",
   "COLOR": "اللون",
   "COLOR": "اللون",
-  "COORDINATE": "التنسيق",
+  "COORDINATE": "محور",
   "VECTOR": "الاتجاه",
   "VECTOR": "الاتجاه",
   "MATRIX": "المصفوفة",
   "MATRIX": "المصفوفة",
-  "TRANSFORMED": "تحويل",
+  "TRANSFORMED": "محولة",
   "GRAYSCALE": "تدرج الرمادي",
   "GRAYSCALE": "تدرج الرمادي",
   "CLAMP": "تشبيك",
   "CLAMP": "تشبيك",
   "SIZE": "الحجم",
   "SIZE": "الحجم",
-  "NOISE": "ضوضاء",
+  "NOISE": "تشويش",
   "SCALE": "الحجم",
   "SCALE": "الحجم",
-  "SEED": "اصل",
+  "SEED": "أصل",
   "KERNEL": "نواة",
   "KERNEL": "نواة",
-  "KERNEL_VIEW_SUM": "مجموع:",
-  "KERNEL_VIEW_SUM_TOOLTIP": "مجموع كل القيم. من المحتمل أنك تريد أن تحدد قيمة 1 أو 0",
-  "GAIN": "كسب",
-  "PIXEL_COORDINATE": "إحداثيات البكسل",
-  "OUTPUT_NODE": "المخرجات",
-  "NOISE_NODE": "ضوضاء",
-  "ELLIPSE_NODE": "الشكل البيضاوي",
-  "CREATE_IMAGE_NODE": "انشاء صورة",
+  "KERNEL_VIEW_SUM": "المجموع:",
+  "KERNEL_VIEW_SUM_TOOLTIP": "مجموع كل القيم. في الأغلب تريد أن تحدد قيمة 1 أو 0",
+  "GAIN": "المضاعفة",
+  "OUTPUT_NODE": "مخرجات",
+  "NOISE_NODE": "تشويش",
+  "ELLIPSE_NODE": "شكل بيضاوي",
+  "CREATE_IMAGE_NODE": "إنشاء صورة",
   "FOLDER_NODE": "مجلد",
   "FOLDER_NODE": "مجلد",
   "IMAGE_LAYER_NODE": "طبقة الصورة",
   "IMAGE_LAYER_NODE": "طبقة الصورة",
   "MATH_NODE": "رياضيات",
   "MATH_NODE": "رياضيات",
   "MERGE_NODE": "دمج",
   "MERGE_NODE": "دمج",
-  "MODIFY_IMAGE_LEFT_NODE": "ابدأ تعديل الصورة",
-  "MODIFY_IMAGE_RIGHT_NODE": "انهاء تعديل الصورة",
+  "MODIFY_IMAGE_LEFT_NODE": "بداية تعديل الصورة",
+  "MODIFY_IMAGE_RIGHT_NODE": "نهاية تعديل الصورة",
   "COMBINE_CHANNELS_NODE": "دمج القنوات",
   "COMBINE_CHANNELS_NODE": "دمج القنوات",
-  "COMBINE_COLOR_NODE": "الجمع بين اللون",
-  "COMBINE_VECD_NODE": "الجمع بين الاتجاهات",
+  "COMBINE_COLOR_NODE": "دمج  الألوان",
+  "COMBINE_VECD_NODE": "دمج الاتجاهات",
   "COMBINE_VECI_NODE": "دمج الاتجاهات",
   "COMBINE_VECI_NODE": "دمج الاتجاهات",
   "SEPARATE_CHANNELS_NODE": "فصل القنوات",
   "SEPARATE_CHANNELS_NODE": "فصل القنوات",
   "SEPARATE_VECD_NODE": "فصل الاتجاهات",
   "SEPARATE_VECD_NODE": "فصل الاتجاهات",
-  "SEPARATE_VECI_NODE": "فصل الاتجاهات",
+  "SEPARATE_VECI_NODE": "فصل الاتجاهات الصحيحة عدديًا",
   "SEPARATE_COLOR_NODE": "فصل اللون",
   "SEPARATE_COLOR_NODE": "فصل اللون",
   "TIME_NODE": "الوقت",
   "TIME_NODE": "الوقت",
-  "FILTERS": "تحسينات",
+  "FILTERS": "فلاتر",
   "PREVIOUS": "السابق",
   "PREVIOUS": "السابق",
   "FILL": "ملء",
   "FILL": "ملء",
   "MATH_MODE": "وضع الرياضيات",
   "MATH_MODE": "وضع الرياضيات",
-  "NOISE_TYPE": "نوع الضوضاء",
+  "NOISE_TYPE": "نوع التشويش",
   "ACTIVE_FRAME": "الإطار النشط",
   "ACTIVE_FRAME": "الإطار النشط",
-  "NORMALIZED_TIME": "الوقت الطبيعي",
+  "NORMALIZED_TIME": "الوقت الموحّد",
   "BETA_ANIMATIONS": "الرسوم المتحركة",
   "BETA_ANIMATIONS": "الرسوم المتحركة",
-  "SHOW_ALL_EXAMPLES": "عرض الكل",
   "APPLY_FILTER_NODE": "تطبيق الفلتر",
   "APPLY_FILTER_NODE": "تطبيق الفلتر",
   "FILTER": "فلتر",
   "FILTER": "فلتر",
   "FROM": "من",
   "FROM": "من",
-  "TO": "الى",
-  "TIME": "الوقت"
+  "TO": "إلى",
+  "TIME": "الوقت",
+  "EXPORT_SPRITESHEET_HEADER": "ورقة رسوم",
+  "STROKE_COLOR": "لون الحواف",
+  "STROKE_WIDTH": "سُمك الحواف",
+  "ALPHA": "الشفافية",
+  "BIAS": "الانحياز",
+  "TILE_MODE": "وضع البلاطات",
+  "ON_ALPHA": "على الشفافية",
+  "KERNEL_FILTER_NODE": "فلتر النواة",
+  "OCTAVES": "عدد التركيبات",
+  "BUILD_ID": "الرقم التعريفي للبناء: {0}",
+  "ERASE_BLEND_MODE": "مسح",
+  "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "فلتر تحويل المصفوفة",
+  "WITHOUT_FILTERS": "بدون فلاتر",
+  "RAW_LAYER_OUTPUT": "نيء",
+  "POND_EXAMPLE": "بِركة",
+  "TREE_EXAMPLE": "شجرة في الرياح",
+  "OUTLINE_EXAMPLE": "حواف تلقائية",
+  "SLIME_EXAMPLE": "سلايم متحرك",
+  "LERP_NODE": "استيفاء خطي",
+  "GRAYSCALE_FILTER_NODE": "فلتر أبيض وأسود",
+  "WARMING_UP": "يتم الإحماء",
+  "RENDERING_FRAME": "يتم إنشاء الإطار {0}/{1}",
+  "RENDERING_VIDEO": "يتم تكوين الفيديو",
+  "FINISHED": "تم",
+  "GENERATING_SPRITE_SHEET": "يتم إنشاء ورقة الرسوم",
+  "RENDERING_IMAGE": "يتم تكوين الصورة",
+  "PROGRESS_POPUP_TITLE": "التطوّرات",
+  "POINTS": "النقاط",
+  "MIN_DISTANCE": "أصغر مسافة",
+  "MAX_POINTS": "أكبر مسافة",
+  "DISTRIBUTE_POINTS": "توزيع النقاط",
+  "REMOVE_CLOSE_POINTS": "إزالة النقاط القريبة",
+  "RASTERIZE_SHAPE": "التحويل الشكل إلى نقطي",
+  "MODE": "الوضع",
+  "Factor": "المضاعف",
+  "NORMALIZE": "توحيد",
+  "WEIGHT_FACTOR": "الوزن",
+  "STARS_EXAMPLE": "نجوم",
+  "ADD_EMPTY_FRAME": "إضافة إطار فارغ",
+  "DUPLICATE_FRAME": "تكرار الإطار",
+  "DELETE_FRAME": "إزالة الإطار",
+  "DEFAULT_MEMBER_NAME": "عنصر جديد",
+  "NO_PARSER_FOUND": "لم يتم العثور على قارئ ملف للامتداد '{0}'",
+  "SELECT_FILE_FORMAT": "اختيار نوع الملف",
+  "SELECT_FILE_FORMAT_DESCRIPTION": "عدة أنواع ملفات لنفس الامتداد مدعومة. من فضلك اختر النوع الذي تريد استخدامه.",
+  "NEW_PALETTE_FILE": "لوح",
+  "ISLAND_EXAMPLE": "جُزُر",
+  "ONION_FRAMES_COUNT": "أٌطٌر بصلية",
+  "ONION_OPACITY": "مرئية بصلية",
+  "TOGGLE_ONION_SKINNING": "تفعيل/إبطال سلخ البصل",
+  "CHANGE_ACTIVE_FRAME_PREVIOUS": "تبديل الإطار النشط إلى الإطار السابق",
+  "CHANGE_ACTIVE_FRAME_NEXT": "تبديل الإطار النشط إلى الإطار القادم",
+  "TOGGLE_ANIMATION": "تفعيل/إبطال الرسوم المتحركة",
+  "NEW_FROM_CLIPBOARD": "جديد من الحافظة",
+  "OFFSET": "بُعد",
+  "SHAPE": "الشكل",
+  "STRUCTURE": "البنية",
+  "NUMBERS": "الأرقام",
+  "OPERATIONS": "العمليات",
+  "GENERATION": "إنشاء",
+  "NUMBER": "رقم",
+  "ANIMATION": "رسوم متحركة",
+  "SAMPLE_IMAGE": "صورة نمطية",
+  "POSITION": "موقع",
+  "MATH_ADD": "جمع",
+  "MATH_SUBTRACT": "طرح",
+  "MULTIPLY": "ضرب",
+  "DIVIDE": "قسمة",
+  "SIN": "Sin",
+  "COS": "Cos",
+  "TAN": "Tan",
+  "PIXEL_ART_TOOLSET": "فن بكسل",
+  "VECTOR_TOOLSET": "خط اتجاهي",
+  "VECTOR_LAYER": "طبقة خط اتجاهي",
+  "STROKE_COLOR_LABEL": "حواف",
+  "SYNC_WITH_PRIMARY_COLOR_LABEL": "اتفاق مع اللون الرئيسي",
+  "RASTERIZE": "التحويل إلى نقطية",
+  "RASTERIZE_ACTIVE_LAYER": "تحويل الطبقة النشطة إلى نقطية",
+  "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE": "تحويل الطبقة النشطة إلى طبقة صورة (نقطية).",
+  "NEW_ELLIPSE_LAYER_NAME": "بيضوي",
+  "NEW_RECTANGLE_LAYER_NAME": "مستطيل",
+  "NEW_LINE_LAYER_NAME": "خط",
+  "RENDER_OUTPUT": "تكوين المخرجات",
+  "PAINT_TOOLSET": "التلوين",
+  "HARDNESS_SETTING": "القساوة",
+  "SPACING_SETTING": "التباعد",
+  "ANTI_ALIASING_SETTING": "مضاد التعرج",
+  "TOLERANCE_LABEL": "التفاوت المسموح",
+  "TOGGLE_SNAPPING": "تفعيل/إبطال الالتقاط",
+  "HIGH_RES_PREVIEW": "معاينة عالية الجودة",
+  "LOW_RES_PREVIEW": "معاينة جودة المستند",
+  "TOGGLE_HIGH_RES_PREVIEW": "تفعيل/إبطال المعاينة عالية الجودة",
+  "FACTOR": "المضاعف",
+  "PATH_TOOL": "المسار",
+  "PATH_TOOL_TOOLTIP": "إنشاء مسارات خطوط اتجاهية ومنحنيات ({0}).",
+  "PATH_TOOL_ACTION_DISPLAY": "اضغط لوضع نقطة.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL": "انقر على نقطة موجودة واسحبها لجعلها منحنى. انقر على نقطة تحكم لتحديدها.",
+  "PATH_TOOL_ACTION_DISPLAY_SHIFT": "اضغط لوضع طبقة جديدة.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL_SHIFT": "انقر على نقطة تحكم لأضافتها للتحديد.",
+  "PATH_TOOL_ACTION_DISPLAY_ALT": "انقر على نقطة تحكم وحركها للتعديل على طرف واحد فقط للمنحنى.",
+  "DEFAULT_PATH_LAYER_NAME": "المسار",
+  "DELETE_NODES": "مسح العٌقَد",
+  "DELETE_NODES_DESCRIPTIVE": "مسح العقد المحددة",
+  "DELETE_CELS": "مسح الرسوم",
+  "DELETE_CELS_DESCRIPTIVE": "مسح الرسوم المحددة",
+  "COPY_COLOR_TO_CLIPBOARD": "نسخ اللون إلى المحفظة",
+  "VIEWPORT_ROTATION": "تدوير إطار العرض",
+  "NEXT_TOOL_SET": "مجموعة الأدوات التالية",
+  "PREVIOUS_TOOL_SET": "مجموعة  الأدوات السابقة",
+  "FILL_MODE": "نوع الملئ",
+  "USE_LINEAR_SRGB_PROCESSING": "استخدام sRGB خطي لمعالجة الألوان",
+  "USE_LINEAR_SRGB_PROCESSING_DESC": "تحويل المستند باستخدام وضع المزج sRGB إلى sRGB خطي لمعالجة الألوان. هذا سيؤثر على ألوان العنصر، لكنه سيجعل المزج أكثر دقة.",
+  "FILL_TYPE_WINDING": "متعرج",
+  "FILL_TYPE_EVEN_ODD": "زوجي فردي",
+  "FILL_TYPE_INVERSE_WINDING": "عكس التعرج",
+  "FILL_TYPE_INVERSE_EVEN_ODD": "زوجي فردي معكوس",
+  "STROKE_CAP": "نهاية الخط",
+  "STROKE_JOIN": "دمج الحواف",
+  "COPY_VISIBLE": "نسخ ما هو مرئي",
+  "COPY_VISIBLE_DESCRIPTIVE": "نسخ البكسلات المرئية",
+  "CREATE_CEL": "إنشاء رسمة",
+  "CREATE_CEL_DESCRIPTIVE": "إنشاء رسمة جديدة",
+  "DUPLICATE_CEL": "تكرار الرسمة",
+  "DUPLICATE_CEL_DESCRIPTIVE": "تكرار الرسمة في الإطار الحالي",
+  "RENDER_PREVIEW": "معاينة التكوين",
+  "OUTPUT_NAME": "اسم المخرجات",
+  "CUSTOM_OUTPUT_NODE": "مخرجات مخصصة",
+  "TOGGLE_HUD": "تفعيل/إبطال HUD",
+  "OPEN_TIMELINE": "فتح شريط الزمن",
+  "OPEN_NODE_GRAPH": "فتح مخطط القعد",
+  "TOGGLE_PLAY": "تشغيل/إيقاف الرسوم المتحركة",
+  "OPEN_PREVIEW_WINDOW": "فتح نافذة المعاينة",
+  "PREVIEW_TITLE": "معاينة",
+  "GREATER_THAN": "أكبر",
+  "LESS_THAN": "أصغر",
+  "LESS_THAN_OR_EQUAL": "أصغر أو يساوي",
+  "COMPARE": "مقارنة",
+  "MATH_POWER": "قوى",
+  "LOGARITHM": "لوغاريذم",
+  "NATURAL_LOGARITHM": "لوغاريذم طبيعي",
+  "ROOT": "جذر",
+  "INVERSE_ROOT": "جذر معكوس",
+  "FRACTION": "جزء",
+  "NEGATE": "عكس الإشارة",
+  "FLOOR": "التقريب للأدنى",
+  "CEIL": "التقريب للأعلى",
+  "ROUND": "التقريب",
+  "MODULO": "باقي القسمة",
+  "STEP": "خطوة",
+  "SMOOTH_STEP": "خطى ملساء",
+  "COPY_NODES": "نسخ العقد",
+  "COPY_NODES_DESCRIPTIVE": "نسخ العقد المحددة",
+  "PASTE_NODES": "لصق العقد",
+  "PASTE_NODES_DESCRIPTIVE": "لصق العقد المنسوخة",
+  "COPY_CELS": "نسخ الرسوم",
+  "COPY_CELS_DESCRIPTIVE": "نسخ الرسوم المحددة",
+  "TOGGLE_ONION_SKINNING_DESCRIPTIVE": "تفعيل/إبطال سلخ البصل",
+  "VALUE": "القيمة",
+  "TARGET": "المراد",
+  "EPSILON": "أبسيلون",
+  "PRESERVE_ALPHA": "الحفاظ على الشفافية",
+  "BLUR_FILTER_NODE": "فلتر تغويش Gaussian",
+  "LENGTH": "الطول",
+  "GREATER_THAN_OR_EQUAL": "أكبر أو يساوي",
+  "WEBP_FILE": "صور WebP",
+  "COLOR_NODE": "اللون",
+  "CONVERT_TO_CURVE": "التحويل إلى منحني",
+  "CONVERT_TO_CURVE_DESCRIPTIVE": "تحويل الخطوط الاتجاهية المحددة إلى منحني/مسار",
+  "FONT_FILES": "ملفات خطوط الكتابة",
+  "UNIT_PT": "نقطة",
+  "FONT_LABEL": "الفصيلة",
+  "FONT_SIZE_LABEL": "الحجم",
+  "SPACING_LABEL": "التباعد",
+  "TEXT_TOOL": "كتابة",
+  "MISSING_FONT": "خط كتابة مفقود",
+  "TEXT_LAYER_NAME": "كتابة",
+  "TEXT_TOOL_TOOLTIP": "إنشاء كتابة ({0}).",
+  "BOLD_TOOLTIP": "غامق",
+  "ITALIC_TOOLTIP": "مائل",
+  "CUSTOM_FONT": "خط كتابة مخصص",
+  "USE_SRGB_PROCESSING": "استخدام sRGB لمعالجة الألوان",
+  "USE_SRGB_PROCESSING_DESC": "تحويل معالجة الألوان في المستند من sRGB خطي إلى sRGB. سيؤثر هذا في ألوان العنصر.",
+  "TEXT_NODE": "نص",
+  "TEXT_LABEL": "النص",
+  "HIGH_DPI_RENDERING": "تكوين بجودة عالية",
+  "THICKNESS": "السمك",
+  "TYPE": "النوع",
+  "EFFECTS": "تأثيرات",
+  "OUTLINE_NODE": "حواف",
+  "SHADER_CODE": "رمز المظلل",
+  "SHADER_NODE": "مظلل",
+  "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "فشل فتح الملف",
+  "FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE": "فشل تعديل سلسلة المحارف هذه. السبب: {0}",
+  "STRING_EDIT_IN_DEFAULT_APP": "التعديل في التطبيق الافتراضي",
+  "STRING_OPEN_IN_FOLDER": "الفتح في المجلد",
+  "DISCO_BALL_EXAMPLE": "كرة ديسكو",
+  "COLOR_SPACE": "المجال اللوني",
+  "PHOTO_EXAMPLES": "صورة",
+  "MASK_EXAMPLE": "قناع",
+  "SHADOW_NODE": "فلتر ظل",
+  "INPUT_MATRIX": "المصفوفة المدخلة",
+  "OUTPUT_MATRIX": "المصفوفة المخرجة",
+  "CENTER": "المنتصف",
+  "CANVAS_POSITION": "موقع اللوحة",
+  "CENTER_POSITION": "موقع منتصف",
+  "TILE_MODE_X": "الوضع الأفقي للبلاط",
+  "TILE_MODE_Y": "الوضع العمودي للبلاط",
+  "TILE_NODE": "بلاط",
+  "SKEW": "عوج",
+  "OFFSET_NODE": "بُعد",
+  "SKEW_NODE": "عوج",
+  "SCALE_NODE": "تحجيم",
+  "ROTATE_NODE": "تدوير",
+  "TRANSFORM_NODE": "تحويل",
+  "UNIT": "الوحدة",
+  "ANGLE": "الزاوية",
+  "DOCUMENT_INFO_NODE": "معلومات المستند",
+  "MASK_NODE": "قناع",
+  "INTENSITY": "الشدة",
+  "INVERT_FILTER_NODE": "فلتر العكس",
+  "COLOR_ADJUSTMENTS_FILTER": "فلتر تعديل الألوان",
+  "ADJUST_BRIGHTNESS": "تعديل السطوع",
+  "ADJUST_CONTRAST": "تعديل التفاوت",
+  "ADJUST_SATURATION": "تعديل التشبع",
+  "ADJUST_TEMPERATURE": "تعديل الدفئ",
+  "ADJUST_TINT": "تعديل الصبغة",
+  "ADJUST_HUE": "تعديل مؤشر اللون",
+  "HUE_VALUE": "مؤشر اللون",
+  "SATURATION_VALUE": "التشبع",
+  "BRIGHTNESS_VALUE": "السطوع",
+  "CONTRAST_VALUE": "التفاوت",
+  "TEMPERATURE_VALUE": "الدفء",
+  "TINT_VALUE": "الصبغة",
+  "DELETE_SELECTED": "مسح المحدد",
+  "DELETE_SELECTED_DESCRIPTIVE": "مسح العنصر المحدد (الطبقة، البكسل، إلخ)",
+  "GRIDLINES_SIZE": "حجم التقسيم",
+  "CANVAS": "اللوحة",
+  "UNEXPECTED_SHUTDOWN": "انغلاق غير متوقع",
+  "UNEXPECTED_SHUTDOWN_MSG": "لقد أُغلق PixiEditor بشكل غير متوقع. قمنا بتحميل آخر حفظ تلقائي لملفاتك.",
+  "OK": "حسنًا",
+  "OPEN_AUTOSAVES": "تصفح الحفظ التلقائي",
+  "AUTOSAVE_SETTINGS_HEADER": "حفظ تلقائي",
+  "AUTOSAVE_SETTINGS_SAVE_STATE": "فتح آخر ملفات عند البدئ",
+  "AUTOSAVE_SETTINGS_PERIOD": "فترة الحفظ التلقائي",
+  "AUTOSAVE_ENABLED": "تم تفعيل الحفظ التلقائي",
+  "MINUTE_UNIVERSAL": "دقيقة",
+  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "حفظ تلقائي للملف المحدد",
+  "LOAD_LAZY_FILE_MESSAGE": "لتسريع البدئ، PixiEditor لم يحمل هذا الملف. اضغط على الزر أدناه لتحميله.",
+  "EASING_NODE": "تخفيف الحركة",
+  "EASING_TYPE": "نوع تخفيف الحركة",
+  "OPEN_DIRECTORY_ON_EXPORT": "فتح الدليل عند التصدير",
+  "ERROR_LOOP_DETECTED_MESSAGE": "تحريك هذه الطبقة سينشئ حلقة مفرغة. أصلح ذلك في مخطط العقد.",
+  "LINEAR_EASING_TYPE": "خطي",
+  "IN_SINE_EASING_TYPE": "Sine داخلة",
+  "OUT_SINE_EASING_TYPE": "Sine خارجة",
+  "IN_OUT_SINE_EASING_TYPE": "Sine داخلة وخارجة",
+  "IN_QUAD_EASING_TYPE": "تربيعية داخلة",
+  "OUT_QUAD_EASING_TYPE": "تربيعية خارجة",
+  "IN_OUT_QUAD_EASING_TYPE": "تربيعية داخلة وخارجة",
+  "IN_CUBIC_EASING_TYPE": "تكعيبية داخلة",
+  "OUT_CUBIC_EASING_TYPE": "تكعيبية خارجة",
+  "IN_OUT_CUBIC_EASING_TYPE": "تكعيبية داخلة وخارجة",
+  "IN_QUART_EASING_TYPE": "درجة رابعة داخلة",
+  "OUT_QUART_EASING_TYPE": "درجة رابعة خارجة",
+  "IN_OUT_QUART_EASING_TYPE": "درجة رابعة داخلة وخارجة",
+  "IN_QUINT_EASING_TYPE": "درجة خامسة داخلة",
+  "OUT_QUINT_EASING_TYPE": "درجة خامسة خارجة",
+  "IN_OUT_QUINT_EASING_TYPE": "درجة خامسة داخلة وخارجة",
+  "IN_EXPO_EASING_TYPE": "أسيّة داخلة",
+  "OUT_EXPO_EASING_TYPE": "أسيّة خارجة",
+  "IN_OUT_EXPO_EASING_TYPE": "أسيّة داخلة وخارجة",
+  "IN_CIRC_EASING_TYPE": "دائرية داخلة",
+  "OUT_CIRC_EASING_TYPE": "دائرية خارجة",
+  "IN_OUT_CIRC_EASING_TYPE": "دائرية داخلة وخارجة",
+  "IN_BACK_EASING_TYPE": "متعدية داخلة",
+  "OUT_BACK_EASING_TYPE": "متعدية خارجة",
+  "IN_OUT_BACK_EASING_TYPE": "متعدية داخلة وخارجة",
+  "IN_ELASTIC_EASING_TYPE": "مطاطية داخلة",
+  "OUT_ELASTIC_EASING_TYPE": "مطاطية خارجة",
+  "IN_OUT_ELASTIC_EASING_TYPE": "مطاطية داخلة وخارجة",
+  "IN_BOUNCE_EASING_TYPE": "مرتدة داخلة",
+  "OUT_BOUNCE_EASING_TYPE": "مرتدة خارجة",
+  "IN_OUT_BOUNCE_EASING_TYPE": "مرتدة داخلة وخارجة",
+  "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
+  "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
+  "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
+  "COLOR_MANAGED_COLOR_SAMPLE_MODE": "حسب اللون",
+  "RAW_COLOR_SAMPLE_MODE": "خام",
+  "FRACTAL_PERLIN_NOISE_TYPE": "تشويش Perlin",
+  "TURBULENCE_PERLIN_NOISE_TYPE": "اضطراب",
+  "INHERIT_COLOR_SPACE_TYPE": "موروث",
+  "SRGB_COLOR_SPACE_TYPE": "sRGB",
+  "LINEAR_SRGB_COLOR_SPACE_TYPE": "sRGB خطي",
+  "SIMPLE_OUTLINE_TYPE": "بسيط",
+  "GAUSSIAN_OUTLINE_TYPE": "غاوسية",
+  "PIXEL_PERFECT_OUTLINE_TYPE": "بكسل مثالي",
+  "DEGREES_ROTATION_TYPE": "الدرجات",
+  "RADIANS_ROTATION_TYPE": "راديان",
+  "WEIGHTED_GRAYSCALE_MODE": "موزون",
+  "AVERAGE_GRAYSCALE_MODE": "المتوسط",
+  "CUSTOM_GRAYSCALE_MODE": "مخصص",
+  "CLAMP_TILE_MODE": "تثبيت",
+  "REPEAT_TILE_MODE": "تكرار",
+  "MIRROR_TILE_MODE": "مرآة",
+  "ERR_UNKNOWN_FILE_FORMAT": "نمط ملف غير معروف",
+  "ERR_EXPORT_SIZE_INVALID": "حجم تصدير غير صحيح. على القيم أن تكون أكبر من ال0.",
+  "ERR_UNKNOWN_IMG_FORMAT": "نمط صورة غير معروف '{0}'.",
+  "ERR_FAILED_GENERATE_SPRITE_SHEET": "فشل إنشاء ورقة الرسوم",
+  "ERR_NO_RENDERER": "لم يتم العثور على مكوّن الرسوم المتحركة",
+  "ERR_RENDERING_FAILED": "فشل التكوين",
+  "ENABLE_ANALYTICS": "إرسال بيانات مبنية للمجهول",
+  "ANALYTICS_INFO": "نجمع بيانات استخدام مبنية للمجهول لتحسين PixiEditor. لا تُجمع أي بيانات شخصية.",
+  "LANGUAGE_INFO": "كل الترجمات من عمل الجمهور. انضم إلى الـDiscord server خاصتنا لمزيد من المعلومات.",
+  "UP_TO_DATE_UNKNOWN": "لم يمكن التحقق من وجود تحديثات",
+  "UP_TO_DATE": "PixiEditor في أحدث نسخة",
+  "UPDATE_AVAILABLE": "التحديث {0} متوفر",
+  "UPDATE_FAILED_DOWNLOAD": "فشل تنزيل التحديث",
+  "UPDATE_READY_TO_INSTALL": "التحديث حاضر. الانتقال إلى {0}؟",
+  "SWITCH_TO_NEW_VERSION": "الانتقال",
+  "DOWNLOAD_UPDATE": "تنزيل",
+  "CHECKING_FOR_UPDATES": "التحقق من وجود تحديثات...",
+  "PAINT_SHAPE_SETTING": "شكل الفرشاة",
+  "BOOL_OPERATION_NODE": "عملية منطقية",
+  "FIRST_SHAPE": "الشكل الأول",
+  "SECOND_SHAPE": "الشكل الثاني",
+  "OPERATION": "العملية",
+  "UNION_VECTOR_PATH_OP": "اتحاد",
+  "DIFFERENCE_VECTOR_PATH_OP": "فرق",
+  "INTERSECT_VECTOR_PATH_OP": "تقاطع",
+  "XOR_VECTOR_PATH_OP": "XOR",
+  "REVERSE_DIFFERENCE_VECTOR_PATH_OP": "عكس الفرق",
+  "NO_DOCUMENT_OPEN": "لا شيء هنا",
+  "EMPTY_DOCUMENT_ACTION_BTN": "ابدأ بالإبداع",
+  "ONBOARDING_TITLE": "أهلًا بك إلى",
+  "ONBOARDING_DESCRIPTION": "دعنا ننصب مساحة عملك!",
+  "ONBOARDING_SKIP_BTN": "تجاوز",
+  "ONBOARDING_ACTION_BTN": "هيا نبدأ",
+  "ONB_SELECT_PRIMARY_TOOLSET": "حدد مجموعة أدواتك الرئيسية",
+  "ONB_NEXT_BTN": "التالي",
+  "ONB_BACK_BTN": "السابق",
+  "ONB_ANALYTICS": "بيانات مبنية للمجهول",
+  "ONB_ALL_SET": "أنت جاهز!",
+  "ONB_ALL_SET_BTN": "ابدأ بالإبداع",
+  "ANALYTICS_INFO_DETAILED": "نجمع بيانات استخدام مبنية للمجهول لتحسين البرنامج. البيانات خالية من أي بيانات شخصية. بعض المعلومات التي يتعقبها PixiEditor تتضمن:\n- أي الأدوات تُستعمل وكيف يتم استعمالها\n- طول الوقت الذي استخدمت فيه البرنامج\n- أي التعليمات تُستعمل\n- بيانات الأداء\n\nيمكنك إلغاء تعقب البيانات حينما شئت في الإعدادات.",
+  "PRIVACY_POLICY": "سياسة الخصوصية",
+  "ONB_SHORTCUTS": "حدد اختصاراتك",
+  "PRIMARY_TOOLSET": "مجموعة الأدوات الرئيسية",
+  "AUTO_SCALE_BACKGROUND": "تحجيم تلقائي للخلفية",
+  "UPDATES": "تحديثات",
+  "SCENE": "مشهد",
+  "CUSTOM_BACKGROUND_SCALE": "تحجيم مخصص للخلفية",
+  "PRIMARY_BG_COLOR": "لون الخلفية الرئيسي",
+  "SECONDARY_BG_COLOR": "لون الخلفية الثانوي",
+  "RESET": "إعادة الضبط",
+  "AUTOSAVE_OPEN_FOLDER": "فتح مجلّد الحفظ التلقائي",
+  "AUTOSAVE_OPEN_FOLDER_DESCRIPTIVE": "فتح المجلد الذي تخزن فيه ملفات الحفظ التلقائي",
+  "AUTOSAVE_TOGGLE_DESCRIPTIVE": "تفعيل/إبطال الحفظ التلقائي",
+  "OPEN_TYPE_FONT": "خطوط OpenType",
+  "TRUE_TYPE_FONT": "خطوط TrueType",
+  "PROCEDURAL_GENERATION": "تحريك إجرائي",
+  "COLOR_MATRIX_FILTER_NODE": "فلتر مصفوفة الألوان",
+  "WORKSPACE": "مساحة العمل",
+  "EXPORT_OUTPUT": "تصدير المخرجات",
+  "RENDER_OUTPUT_SIZE": "حجم المخرجات المكوَّنة",
+  "RENDER_OUTPUT_CENTER": "منتصف المخرجات المكوَّنة",
+  "COLOR_PICKER": "منتقي اللون",
+  "UNAUTHORIZED_ACCESS": "دخول غير مصرح به",
+  "SEPARATE_SHAPES": "فصل الأشكال",
+  "SEPARATE_SHAPES_DESCRIPTIVE": "فصل الأشكال من الخط الاتجاهي الحالى إلى طبقات منفصلة",
+  "TEXT": "نص",
+  "EXTRACT_SELECTED_TEXT": "استخلاص النص المحدد",
+  "EXTRACT_SELECTED_TEXT_DESCRIPTIVE": "استخلاص النص المحدد إلى طبقة جديدة.",
+  "EXTRACT_SELECTED_CHARACTERS": "استخلاص الأحرف المحددة",
+  "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE": "استخلاص الأحرف المحددة إلى طبقات جديدة.",
+  "STEP_START": "تراجع إلى الرسمة الأقرب",
+  "STEP_END": "تقدّم إلى الرسمة الأقرب",
+  "STEP_FORWARD": "تقدّم إطارًا",
+  "STEP_BACK": "تراجع إطارًا",
+  "ONB_FINISH_BTN": "إنهاء",
+  "USER_NOT_FOUND": "الرجاء إدخال عنوان البريد الالكتروني الذي استخدمته لشراء نسخة المؤسس.",
+  "SESSION_NOT_VALID": "الجلسة غير صحيحة، الرجاء تسجيل الدخول مجددًا",
+  "SESSION_NOT_FOUND": "لم يتم العثور على الجلسة، جرب تسجيل الدخول مجددًا",
+  "TOO_MANY_REQUESTS": "طلبات كثيرة جدًا. حاول مجددًا بعد {0} ثوانٍ.",
+  "SESSION_EXPIRED": "انتهت صلاحية الجلسة. الرجاء تسجيل الدخول مجددًا.",
+  "CONNECTION_ERROR": "خطأ في الاتصال. الرجاء تفقد اتصالك بالانترنت.",
+  "FAIL_LOAD_USER_DATA": "فشل تحميل بيانات المستخدم المحفوظة",
+  "LOGOUT": "تسجيل الخروج",
+  "LOGGED_IN_AS": "مرحبًا",
+  "EMAIL_SENT": "تم إرسال البريد الالكتروني! تفقد بريدك الوارد.",
+  "RESEND_ACTIVATION": "إعادة الإرسال",
+  "INVALID_TOKEN": "الجلسة غير صحيحة أو انتهت صلاحيتها. الرجاء تسجيل الدخول مجددًا.",
+  "ENTER_EMAIL": "أدخل عنوان بريدك الالكتروني",
+  "LOGIN_LINK": "إرسال رابط تسجيل الدخول",
+  "LOGIN_LINK_INFO": "سنرسل إليك بريدًا الكترونيًا برابط أمين لتسجيل الدخول. لا حاجة لكلمة مرور.",
+  "ACCOUNT_WINDOW_TITLE": "الحساب",
+  "OPEN_ACCOUNT_WINDOW": "إدارة الحساب",
+  "INSTALL": "تنصيب",
+  "OWNED_PRODUCTS": "المحتوى المملوك",
+  "INSTALLING": "يتم التنصيب",
+  "INSTALLED": "تم التنصيب",
+  "ACCOUNT_PROVIDER_INFO": "الحساب يديره",
+  "UPDATE": "تحديث",
+  "FOUNDERS_BUNDLE_SUBTEXT": "ادعم PixiEditor وعزز إنتاجيتك!",
+  "BECOME_A_FOUNDER": "كن مؤسسًا",
+  "LOGIN": "تسجيل الدخول",
+  "NOT_FOUNDER_YET": "لست مؤسسًا بعد؟",
+  "VERY_LOW_QUALITY_PRESET": "واطئ جدًا",
+  "LOW_QUALITY_PRESET": "واطئ",
+  "MEDIUM_QUALITY_PRESET": "وسط",
+  "HIGH_QUALITY_PRESET": "عالٍ",
+  "VERY_HIGH_QUALITY_PRESET": "عالٍ جدًا",
+  "EXPORT_FRAMES": "تصدير الأُطر",
+  "OLD_MIN": "الحد الأدنى القديم",
+  "OLD_MAX": "الحد الأعلى القديم",
+  "NEW_MIN": "الحد الأدنى الجديد",
+  "NEW_MAX": "الحد الأعلى الجديد",
+  "REMAP_NODE": "إعادة تخطيط",
+  "PASTE_CELS": "لصق الرسوم",
+  "SCALE_X": "تحجيم أفقي",
+  "SCALE_Y": "تحجيم عمودي",
+  "TRANSLATE_X": "التحريك أفقيًا",
+  "TRANSLATE_Y": "التحريك عموديًا",
+  "SKEW_X": "الإمالة أفقيًا",
+  "SKEW_Y": "الإمالة عموديًا",
+  "PERSPECTIVE_0": "المنظور 0",
+  "PERSPECTIVE_1": "المنظور 1",
+  "PERSPECTIVE_2": "المنظور 2",
+  "COMPOSE_MATRIX": "تركيب المصفوفة",
+  "DECOMPOSE_MATRIX": "تفكيك المصفوفة",
+  "TRANSFORMED_POSITION": "الموقع المتحول",
+  "LINE_NODE": "خط",
+  "LINE_START": "البداية",
+  "LINE_END": "النهاية",
+  "RECTANGLE_NODE": "مستطيل",
+  "VORONOI_NOISE_TYPE": "تشويش Voronoi",
+  "VORONOI_FEATURE": "الخاصية",
+  "F1_VORONOI_FEATURE": "F1",
+  "F2_VORONOI_FEATURE": "F2",
+  "F2_MINUS_F1_VORONOI_FEATURE": "F1-F2",
+  "RANDOMNESS": "العشوائية",
+  "ANGLE_OFFSET": "بُعد الزاوية",
+  "PASTE_CELS_DESCRIPTIVE": "لصق الرسوم من المحفظة إلى الإطار الحالي",
+  "ERROR_SAVING_PREFERENCES": "فشل حفظ التفضيلات",
+  "PERFORMANCE": "الأداء",
+  "DISABLE_PREVIEWS": "إبطال المعاينات",
+  "INVERT_MASK": "عكس القناع",
+  "TOGGLE_TINTING_SELECTION": "تفعيل/إبطال تصبيغ المُحَدَّد",
+  "TOGGLE_TINTING_SELECTION_DESCRIPTIVE": "تفعيل/إبطال تصبيغ المُحَدَّد",
+  "TINT_SELECTION": "تصبيغ المُحَدَّد",
+  "PAINT_BRUSH_SHAPE_CIRCLE": "دائرة",
+  "PAINT_BRUSH_SHAPE_SQUARE": "مربع",
+  "BRIGHTNESS_MODE_DEFAULT": "الافتراضي",
+  "BRIGHTNESS_MODE_REPEAT": "تكرار",
+  "ROUND_STROKE_CAP": "مدوّر",
+  "BUTT_STROKE_CAP": "مقصور",
+  "SQUARE_STROKE_CAP": "مربع",
+  "ROUND_STROKE_JOIN": "مدوّر",
+  "MITER_STROKE_JOIN": "مشطوبة",
+  "BEVEL_STROKE_JOIN": "مشطوفة",
+  "TOGGLE_FULLSCREEN": "تفعيل/إبطال نمط ملئ الشاشة",
+  "TOGGLE_FULLSCREEN_DESCRIPTIVE": "تفعيل أو إبطال وضع ملئ الشاشة"
 }
 }

+ 0 - 28
src/PixiEditor/Data/Localization/Languages/cs.json

@@ -56,11 +56,8 @@
   "DECREASE_TOOL_SIZE": "Zmenšit velikost nástroje",
   "DECREASE_TOOL_SIZE": "Zmenšit velikost nástroje",
   "DOWNLOADING_UPDATE": "Stahování aktualizace...",
   "DOWNLOADING_UPDATE": "Stahování aktualizace...",
   "UPDATE_READY": "Je tu nová aktualizace. Chceš aby jsme jí pro tebe nainstalovali teď nebo až později?",
   "UPDATE_READY": "Je tu nová aktualizace. Chceš aby jsme jí pro tebe nainstalovali teď nebo až později?",
-  "NEW_UPDATE": "Nová aktualizace",
   "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Potřebujeme pro dokončení aktualizace povolení od Admina. Spusťte tedy PixiEditor jako administrátor.",
   "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Potřebujeme pro dokončení aktualizace povolení od Admina. Spusťte tedy PixiEditor jako administrátor.",
   "INSUFFICIENT_PERMISSIONS": "Nedostatečná oprávnění",
   "INSUFFICIENT_PERMISSIONS": "Nedostatečná oprávnění",
-  "UPDATE_CHECK_FAILED": "Při hledání aktualizací se něco pokazilo",
-  "COULD_NOT_CHECK_FOR_UPDATES": "Z nějakého důvodu nejsme schopni zjistit jestli je nějaká nová aktualizace.",
   "VERSION": "Verze {0}",
   "VERSION": "Verze {0}",
   "OPEN_TEMP_DIR": "Otevřít složku \"temp\"",
   "OPEN_TEMP_DIR": "Otevřít složku \"temp\"",
   "OPEN_LOCAL_APPDATA_DIR": "Otevřít složku \"Local AppData\"",
   "OPEN_LOCAL_APPDATA_DIR": "Otevřít složku \"Local AppData\"",
@@ -97,11 +94,8 @@
   "SHORTCUT_TEMPLATES": "Šablony klávesových zkratek",
   "SHORTCUT_TEMPLATES": "Šablony klávesových zkratek",
   "RESET_ALL": "Resetovat vše",
   "RESET_ALL": "Resetovat vše",
   "LAYER": "Vrstva",
   "LAYER": "Vrstva",
-  "LAYER_DELETE_SELECTED": "Smazat aktivní vrstvu/složku",
-  "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Smazat aktivní vrstvu nebo složku",
   "LAYER_DELETE_ALL_SELECTED": "Smazat všechny vybrané vrstvy/složky",
   "LAYER_DELETE_ALL_SELECTED": "Smazat všechny vybrané vrstvy/složky",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Smazat všechny vybrané vrstvy a/nebo složky",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Smazat všechny vybrané vrstvy a/nebo složky",
-  "DELETE_SELECTED_PIXELS": "Odstranit vybrané pixely",
   "NEW_FOLDER": "Nová složka",
   "NEW_FOLDER": "Nová složka",
   "CREATE_NEW_FOLDER": "Vytvořit složku",
   "CREATE_NEW_FOLDER": "Vytvořit složku",
   "NEW_LAYER": "Nová vrstva",
   "NEW_LAYER": "Nová vrstva",
@@ -176,7 +170,6 @@
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Zkopírovat vedlejší barvu jako RGB kód",
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Zkopírovat vedlejší barvu jako RGB kód",
   "PALETTE_COLORS": "Barvy palety",
   "PALETTE_COLORS": "Barvy palety",
   "REPLACE_SECONDARY_BY_PRIMARY": "Nahradit vedlejší barvu barvou vedlejší",
   "REPLACE_SECONDARY_BY_PRIMARY": "Nahradit vedlejší barvu barvou vedlejší",
-  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "Nahradit vedlejší barvu barvou vedlejší",
   "REPLACE_PRIMARY_BY_SECONDARY": "Nahradit hlavní barvu barvou vedlejší",
   "REPLACE_PRIMARY_BY_SECONDARY": "Nahradit hlavní barvu barvou vedlejší",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Nahradit hlavní barvu barvou vedlejší",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Nahradit hlavní barvu barvou vedlejší",
   "OPEN_PALETTE_BROWSER": "Otevřít prohlížeč palet",
   "OPEN_PALETTE_BROWSER": "Otevřít prohlížeč palet",
@@ -444,7 +437,6 @@
   "DELETE_PALETTE_CONFIRMATION": "Chceš opravdu odstranit tuhle paletu? Odstranění je na vždy tak pozor.",
   "DELETE_PALETTE_CONFIRMATION": "Chceš opravdu odstranit tuhle paletu? Odstranění je na vždy tak pozor.",
   "SHORTCUTS_IMPORTED": "Klávesové zkratky z {0} byly importované úspěšně.",
   "SHORTCUTS_IMPORTED": "Klávesové zkratky z {0} byly importované úspěšně.",
   "SHORTCUT_PROVIDER_DETECTED": "Zjistili jsme, že máš nainstalovaný {0}. Chceš aby jsme z toho importovali klávesové zkratky?",
   "SHORTCUT_PROVIDER_DETECTED": "Zjistili jsme, že máš nainstalovaný {0}. Chceš aby jsme z toho importovali klávesové zkratky?",
-  "IMPORT_FROM_INSTALLATION": "Importovat z instalace",
   "IMPORT_INSTALLATION_OPTION1": "Importovat z instalace",
   "IMPORT_INSTALLATION_OPTION1": "Importovat z instalace",
   "IMPORT_INSTALLATION_OPTION2": "Použít výchozí nastavení",
   "IMPORT_INSTALLATION_OPTION2": "Použít výchozí nastavení",
   "IMPORT_FROM_TEMPLATE": "Importovat z šablony",
   "IMPORT_FROM_TEMPLATE": "Importovat z šablony",
@@ -467,7 +459,6 @@
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Tahle klávesová zkratka je již použita u '{0}'\nPřejete si nahradit existující klávesovou zkratku?",
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Tahle klávesová zkratka je již použita u '{0}'\nPřejete si nahradit existující klávesovou zkratku?",
   "UNSAVED_CHANGES": "Neuložené změny",
   "UNSAVED_CHANGES": "Neuložené změny",
   "DOCUMENT_MODIFIED_SAVE": "Dokument byl upraven. Chcete změny uložit?",
   "DOCUMENT_MODIFIED_SAVE": "Dokument byl upraven. Chcete změny uložit?",
-  "SESSION_UNSAVED_DATA": "{0} s neuloženými daty. Jste si jisti?",
   "PROJECT_MAINTAINERS": "Správci projektu",
   "PROJECT_MAINTAINERS": "Správci projektu",
   "OTHER_AWESOME_CONTRIBUTORS": "A další úžasní přispěvatelé",
   "OTHER_AWESOME_CONTRIBUTORS": "A další úžasní přispěvatelé",
   "HELP": "Pomoc",
   "HELP": "Pomoc",
@@ -482,7 +473,6 @@
   "TOGGLE_VERTICAL_SYMMETRY": "Přepnout vertikální symetrii",
   "TOGGLE_VERTICAL_SYMMETRY": "Přepnout vertikální symetrii",
   "TOGGLE_HORIZONTAL_SYMMETRY": "Zapnout vodorovnou symetrii",
   "TOGGLE_HORIZONTAL_SYMMETRY": "Zapnout vodorovnou symetrii",
   "RESET_VIEWPORT": "Resetovat pole pohledu",
   "RESET_VIEWPORT": "Resetovat pole pohledu",
-  "VIEWPORT_SETTINGS": "Nastavení pohledu",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Klikni a drž tlačítko myši pro přesouvání pixelů ve vybraných vrstvách.",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Klikni a drž tlačítko myši pro přesouvání pixelů ve vybraných vrstvách.",
   "CTRL_KEY": "Ctrl",
   "CTRL_KEY": "Ctrl",
   "SHIFT_KEY": "Shift",
   "SHIFT_KEY": "Shift",
@@ -507,7 +497,6 @@
   "SECURITY_ERROR_MSG": "Nejsou práva do této určené lokace",
   "SECURITY_ERROR_MSG": "Nejsou práva do této určené lokace",
   "IO_ERROR": "IO chyba",
   "IO_ERROR": "IO chyba",
   "IO_ERROR_MSG": "Chyba při zápisu a disk.",
   "IO_ERROR_MSG": "Chyba při zápisu a disk.",
-  "FAILED_ASSOCIATE_PIXI": "Nepodařilo se spojit .pixi soubor s PixiEditor.",
   "COULD_NOT_SAVE_PALETTE": "Nastala chyba při ukládání palety.",
   "COULD_NOT_SAVE_PALETTE": "Nastala chyba při ukládání palety.",
   "NO_COLORS_TO_SAVE": "Nejsou žádné barvy k uložení",
   "NO_COLORS_TO_SAVE": "Nejsou žádné barvy k uložení",
   "SINGLE_LAYER": "Jediná vrstva",
   "SINGLE_LAYER": "Jediná vrstva",
@@ -547,7 +536,6 @@
   "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Klikni pro výběr barvy z referenční vrstvy.",
   "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Klikni pro výběr barvy z referenční vrstvy.",
   "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Klikni pro výběr barvy z plátna.",
   "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Klikni pro výběr barvy z plátna.",
   "LOCALIZATION_DEBUG_WINDOW_TITLE": "Lokalizační Debug okno",
   "LOCALIZATION_DEBUG_WINDOW_TITLE": "Lokalizační Debug okno",
-  "COMMAND_DEBUG_WINDOW_TITLE": "Okno Command debug",
   "SHORTCUTS_TITLE": "Zkratky",
   "SHORTCUTS_TITLE": "Zkratky",
   "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Přetažením úchytů transformujte měřítko. Podržením klávesy Ctrl a přetažením úchytu jej můžete libovolně přesouvat. Podržením klávesy Shift proporcionálně škálujete. Podržením klávesy Alt a tažením bočního úchytu stříháte. Přetažením vnějších úchytů otáčíte.",
   "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Přetažením úchytů transformujte měřítko. Podržením klávesy Ctrl a přetažením úchytu jej můžete libovolně přesouvat. Podržením klávesy Shift proporcionálně škálujete. Podržením klávesy Alt a tažením bočního úchytu stříháte. Přetažením vnějších úchytů otáčíte.",
   "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Přetažením úchytů transformujte měřítko. Pro proporcionální škálování podržte Shift. Podržte Alt a přetáhněte boční úchyt pro střih. Tažením vnějších úchytů otáčíte.",
   "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Přetažením úchytů transformujte měřítko. Pro proporcionální škálování podržte Shift. Podržte Alt a přetáhněte boční úchyt pro střih. Tažením vnějších úchytů otáčíte.",
@@ -595,7 +583,6 @@
   "BACKGROUND": "Pozadí",
   "BACKGROUND": "Pozadí",
   "OPACITY": "Průhlednost",
   "OPACITY": "Průhlednost",
   "IS_VISIBLE": "Je vidět",
   "IS_VISIBLE": "Je vidět",
-  "CLIP_TO_MEMBER_BELOW": "Připnout k členovi níže",
   "BLEND_MODE": "Režim prolínání",
   "BLEND_MODE": "Režim prolínání",
   "MASK": "Maska",
   "MASK": "Maska",
   "MASK_IS_VISIBLE": "Maska je vidět",
   "MASK_IS_VISIBLE": "Maska je vidět",
@@ -632,7 +619,6 @@
   "BIAS": "Bias",
   "BIAS": "Bias",
   "TILE_MODE": "Režim dlaždic",
   "TILE_MODE": "Režim dlaždic",
   "ON_ALPHA": "On Alpha",
   "ON_ALPHA": "On Alpha",
-  "PIXEL_COORDINATE": "Souřadnice pixelu",
   "OUTPUT_NODE": "Výstup",
   "OUTPUT_NODE": "Výstup",
   "NOISE_NODE": "hluk",
   "NOISE_NODE": "hluk",
   "ELLIPSE_NODE": "Elipsa",
   "ELLIPSE_NODE": "Elipsa",
@@ -663,7 +649,6 @@
   "NORMALIZED_TIME": "Normalizovaný čas",
   "NORMALIZED_TIME": "Normalizovaný čas",
   "BUILD_ID": "Build ID: {0}",
   "BUILD_ID": "Build ID: {0}",
   "ERASE_BLEND_MODE": "Vygumovat",
   "ERASE_BLEND_MODE": "Vygumovat",
-  "MODIFY_IMAGE_PAIR_NODE": "Upravit obrázek",
   "WITHOUT_FILTERS": "Bez filtrů",
   "WITHOUT_FILTERS": "Bez filtrů",
   "RAW_LAYER_OUTPUT": "Raw",
   "RAW_LAYER_OUTPUT": "Raw",
   "POND_EXAMPLE": "Jezírko",
   "POND_EXAMPLE": "Jezírko",
@@ -671,7 +656,6 @@
   "OUTLINE_EXAMPLE": "Automatický obrys",
   "OUTLINE_EXAMPLE": "Automatický obrys",
   "BETA_ANIMATIONS": "Animace",
   "BETA_ANIMATIONS": "Animace",
   "SLIME_EXAMPLE": "Animovaný slime",
   "SLIME_EXAMPLE": "Animovaný slime",
-  "SHOW_ALL_EXAMPLES": "Zobrazit vše",
   "APPLY_FILTER_NODE": "Použít filtrování",
   "APPLY_FILTER_NODE": "Použít filtrování",
   "FILTER": "Filter",
   "FILTER": "Filter",
   "GRAYSCALE_FILTER_NODE": "Černobílý filtr",
   "GRAYSCALE_FILTER_NODE": "Černobílý filtr",
@@ -688,7 +672,6 @@
   "POINTS": "Body",
   "POINTS": "Body",
   "MIN_DISTANCE": "Min. vzdálenost",
   "MIN_DISTANCE": "Min. vzdálenost",
   "MAX_POINTS": "Max. body",
   "MAX_POINTS": "Max. body",
-  "PROBABILITY": "Pravděpodobnost",
   "DISTRIBUTE_POINTS": "Rozdělit body",
   "DISTRIBUTE_POINTS": "Rozdělit body",
   "REMOVE_CLOSE_POINTS": "Odstranit blízké body",
   "REMOVE_CLOSE_POINTS": "Odstranit blízké body",
   "RASTERIZE_SHAPE": "Rastrovat tvar",
   "RASTERIZE_SHAPE": "Rastrovat tvar",
@@ -867,7 +850,6 @@
   "INPUT_MATRIX": "Vstup Matrixu",
   "INPUT_MATRIX": "Vstup Matrixu",
   "OUTPUT_MATRIX": "Výstup Matrixu",
   "OUTPUT_MATRIX": "Výstup Matrixu",
   "CENTER": "Střed",
   "CENTER": "Střed",
-  "CONTENT_OFFSET": "Odsadit obsah",
   "CANVAS_POSITION": "Pozice plátna",
   "CANVAS_POSITION": "Pozice plátna",
   "CENTER_POSITION": "Vycentrovat pozici",
   "CENTER_POSITION": "Vycentrovat pozici",
   "TILE_MODE_X": "Režim dlaždic X",
   "TILE_MODE_X": "Režim dlaždic X",
@@ -876,7 +858,6 @@
   "SKEW": "Zkosení",
   "SKEW": "Zkosení",
   "OFFSET_NODE": "Odsazení",
   "OFFSET_NODE": "Odsazení",
   "SKEW_NODE": "Zkosení",
   "SKEW_NODE": "Zkosení",
-  "ROTATION_NODE": "Otáčení",
   "SCALE_NODE": "Měřítko",
   "SCALE_NODE": "Měřítko",
   "ROTATE_NODE": "Otočit",
   "ROTATE_NODE": "Otočit",
   "TRANSFORM_NODE": "Transformovat",
   "TRANSFORM_NODE": "Transformovat",
@@ -900,8 +881,6 @@
   "CONTRAST_VALUE": "Kontrast",
   "CONTRAST_VALUE": "Kontrast",
   "TEMPERATURE_VALUE": "Teplota",
   "TEMPERATURE_VALUE": "Teplota",
   "TINT_VALUE": "Tón",
   "TINT_VALUE": "Tón",
-  "FAILED_DOWNLOADING_UPDATE_TITLE": "Nepodařilo se stáhnout update",
-  "FAILED_DOWNLOADING_UPDATE": "Nepodařilo se stáhnout update. Zkuste to znovu později.",
   "DELETE_SELECTED": "Odstranit vybrané",
   "DELETE_SELECTED": "Odstranit vybrané",
   "DELETE_SELECTED_DESCRIPTIVE": "Odstranit vybraný prvek (vrstvu, pixely, atd.)",
   "DELETE_SELECTED_DESCRIPTIVE": "Odstranit vybraný prvek (vrstvu, pixely, atd.)",
   "GRIDLINES_SIZE": "Velikost mřížky",
   "GRIDLINES_SIZE": "Velikost mřížky",
@@ -952,10 +931,6 @@
   "IN_BOUNCE_EASING_TYPE": "In Bounce",
   "IN_BOUNCE_EASING_TYPE": "In Bounce",
   "OUT_BOUNCE_EASING_TYPE": "Out Bounce",
   "OUT_BOUNCE_EASING_TYPE": "Out Bounce",
   "IN_OUT_BOUNCE_EASING_TYPE": "In Out Bounce",
   "IN_OUT_BOUNCE_EASING_TYPE": "In Out Bounce",
-  "CLAMP_SHADER_TILE_NODE": "Svorka",
-  "REPEAT_SHADER_TILE_NODE": "Opakovat",
-  "MIRROR_SHADER_TILE_NODE": "Zrcadlo",
-  "DECAL_SHADER_TILE_NODE": "Obtisk",
   "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
   "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
   "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
   "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
   "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
   "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
@@ -990,7 +965,6 @@
   "UP_TO_DATE_UNKNOWN": "Nebyli jsme schopni zkontrolovat aktualizace",
   "UP_TO_DATE_UNKNOWN": "Nebyli jsme schopni zkontrolovat aktualizace",
   "UP_TO_DATE": "Používáte nejnovější verzi",
   "UP_TO_DATE": "Používáte nejnovější verzi",
   "UPDATE_AVAILABLE": "Aktualizace {0} je k dispozici",
   "UPDATE_AVAILABLE": "Aktualizace {0} je k dispozici",
-  "CHECKING_UPDATES": "Kontrola aktualizací...",
   "UPDATE_FAILED_DOWNLOAD": "Nepovedlo se stáhnout aktualizaci",
   "UPDATE_FAILED_DOWNLOAD": "Nepovedlo se stáhnout aktualizaci",
   "UPDATE_READY_TO_INSTALL": "Aktualizace je připravena. Přepnout na {0}?",
   "UPDATE_READY_TO_INSTALL": "Aktualizace je připravena. Přepnout na {0}?",
   "SWITCH_TO_NEW_VERSION": "Přepnout",
   "SWITCH_TO_NEW_VERSION": "Přepnout",
@@ -1042,7 +1016,6 @@
   "ERROR_GRAPH": "Při nastavení diagramu nastala chyba. Opravte to v nódovém diagramu",
   "ERROR_GRAPH": "Při nastavení diagramu nastala chyba. Opravte to v nódovém diagramu",
   "COLOR_MATRIX_FILTER_NODE": "Barevný Matrix filtr",
   "COLOR_MATRIX_FILTER_NODE": "Barevný Matrix filtr",
   "WORKSPACE": "Pracovní plocha",
   "WORKSPACE": "Pracovní plocha",
-  "EXPORT_ZONE_NODE": "Exportovací zóna",
   "IS_DEFAULT_EXPORT": "Je Výchozí Export",
   "IS_DEFAULT_EXPORT": "Je Výchozí Export",
   "EXPORT_OUTPUT": "Exportovat výstup",
   "EXPORT_OUTPUT": "Exportovat výstup",
   "RENDER_OUTPUT_SIZE": "Výstupní velikost renderování",
   "RENDER_OUTPUT_SIZE": "Výstupní velikost renderování",
@@ -1081,7 +1054,6 @@
   "CONNECTION_TIMEOUT": "Čas pro připojení vypršel. Zkuste to prosím znovu.",
   "CONNECTION_TIMEOUT": "Čas pro připojení vypršel. Zkuste to prosím znovu.",
   "OPEN_ACCOUNT_WINDOW": "Spravovat účet",
   "OPEN_ACCOUNT_WINDOW": "Spravovat účet",
   "INSTALL": "Instalovat",
   "INSTALL": "Instalovat",
-  "MANAGE_ACCOUNT": "Spravovat",
   "OWNED_PRODUCTS": "Vlastněný obsah",
   "OWNED_PRODUCTS": "Vlastněný obsah",
   "INSTALLING": "Instaluje se",
   "INSTALLING": "Instaluje se",
   "INSTALLED": "Nainstalováno",
   "INSTALLED": "Nainstalováno",

+ 0 - 26
src/PixiEditor/Data/Localization/Languages/de.json

@@ -56,11 +56,8 @@
   "DECREASE_TOOL_SIZE": "Werkzeug verkleinern",
   "DECREASE_TOOL_SIZE": "Werkzeug verkleinern",
   "DOWNLOADING_UPDATE": "Lade Update herunter...",
   "DOWNLOADING_UPDATE": "Lade Update herunter...",
   "UPDATE_READY": "Update ist bereit zum installieren. Möchtest du es jetzt installieren?",
   "UPDATE_READY": "Update ist bereit zum installieren. Möchtest du es jetzt installieren?",
-  "NEW_UPDATE": "Neues update",
   "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Konnte ohne Administartor Berechtigungen nicht updaten. Bitte start PixiEditor als Administrator.",
   "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Konnte ohne Administartor Berechtigungen nicht updaten. Bitte start PixiEditor als Administrator.",
   "INSUFFICIENT_PERMISSIONS": "Fehlende Berechtigungen",
   "INSUFFICIENT_PERMISSIONS": "Fehlende Berechtigungen",
-  "UPDATE_CHECK_FAILED": "Updateprüfung fehlgeschlagen",
-  "COULD_NOT_CHECK_FOR_UPDATES": "Konnte nicht überprüfen, ob ein Update verfügbar ist.",
   "VERSION": "Version {0}",
   "VERSION": "Version {0}",
   "OPEN_TEMP_DIR": "temp Ordner öffnen",
   "OPEN_TEMP_DIR": "temp Ordner öffnen",
   "OPEN_LOCAL_APPDATA_DIR": "Lokalen AppData Ordner öffnen",
   "OPEN_LOCAL_APPDATA_DIR": "Lokalen AppData Ordner öffnen",
@@ -97,11 +94,8 @@
   "SHORTCUT_TEMPLATES": "Vorlagen",
   "SHORTCUT_TEMPLATES": "Vorlagen",
   "RESET_ALL": "Alle zurücksetzten",
   "RESET_ALL": "Alle zurücksetzten",
   "LAYER": "Ebene",
   "LAYER": "Ebene",
-  "LAYER_DELETE_SELECTED": "Ausgewählte Ebene/Ordner löschen",
-  "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Ausgewählte Ebene oder Ordner löschen",
   "LAYER_DELETE_ALL_SELECTED": "Alle ausgewählte Ebenen/Ordner löschen",
   "LAYER_DELETE_ALL_SELECTED": "Alle ausgewählte Ebenen/Ordner löschen",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Alle ausgewählte Ebenen oder Ordner löschen",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Alle ausgewählte Ebenen oder Ordner löschen",
-  "DELETE_SELECTED_PIXELS": "Alle ausgewählten Pixel löschen",
   "NEW_FOLDER": "Neuer Ordner",
   "NEW_FOLDER": "Neuer Ordner",
   "CREATE_NEW_FOLDER": "Neuen Ordner erstellen",
   "CREATE_NEW_FOLDER": "Neuen Ordner erstellen",
   "NEW_LAYER": "Neue Ebene",
   "NEW_LAYER": "Neue Ebene",
@@ -176,7 +170,6 @@
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Zweitfarbe als RGB-Code kopieren",
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Zweitfarbe als RGB-Code kopieren",
   "PALETTE_COLORS": "Palette Farben",
   "PALETTE_COLORS": "Palette Farben",
   "REPLACE_SECONDARY_BY_PRIMARY": "Ersetzte Zweitfarbe mit Primärfarbe",
   "REPLACE_SECONDARY_BY_PRIMARY": "Ersetzte Zweitfarbe mit Primärfarbe",
-  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "Ersetzte Zweitfarbe mit Primärfarbe",
   "REPLACE_PRIMARY_BY_SECONDARY": "Farbe mit Zweitfarbe ersetzen",
   "REPLACE_PRIMARY_BY_SECONDARY": "Farbe mit Zweitfarbe ersetzen",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Primärfarbe mit Zweitfarbe ersetzen",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Primärfarbe mit Zweitfarbe ersetzen",
   "OPEN_PALETTE_BROWSER": "Paletten Browser öffnen",
   "OPEN_PALETTE_BROWSER": "Paletten Browser öffnen",
@@ -444,7 +437,6 @@
   "DELETE_PALETTE_CONFIRMATION": "Soll diese Palette wirklich gelöscht werden? Dies kann nicht rückgängig gemacht werden.",
   "DELETE_PALETTE_CONFIRMATION": "Soll diese Palette wirklich gelöscht werden? Dies kann nicht rückgängig gemacht werden.",
   "SHORTCUTS_IMPORTED": "Tastenbelegung von {0} importiert.",
   "SHORTCUTS_IMPORTED": "Tastenbelegung von {0} importiert.",
   "SHORTCUT_PROVIDER_DETECTED": "Es scheint, als hättest du {0} installiert. Möchtest du die Tastenbelegung von {0} benutzten?",
   "SHORTCUT_PROVIDER_DETECTED": "Es scheint, als hättest du {0} installiert. Möchtest du die Tastenbelegung von {0} benutzten?",
-  "IMPORT_FROM_INSTALLATION": "Aus Installation importieren",
   "IMPORT_INSTALLATION_OPTION1": "Aus Installation importieren",
   "IMPORT_INSTALLATION_OPTION1": "Aus Installation importieren",
   "IMPORT_INSTALLATION_OPTION2": "Standardvorlage benutzten",
   "IMPORT_INSTALLATION_OPTION2": "Standardvorlage benutzten",
   "IMPORT_FROM_TEMPLATE": "Aus Vorlage importieren",
   "IMPORT_FROM_TEMPLATE": "Aus Vorlage importieren",
@@ -467,7 +459,6 @@
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Dieses Tastenkürzel ist schon für '{0}' zugewiesen\nSoll es mit dem aktuellen ersetzt werden?",
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Dieses Tastenkürzel ist schon für '{0}' zugewiesen\nSoll es mit dem aktuellen ersetzt werden?",
   "UNSAVED_CHANGES": "Ungespeicherte änderungen",
   "UNSAVED_CHANGES": "Ungespeicherte änderungen",
   "DOCUMENT_MODIFIED_SAVE": "Das Bild wurde verändert. Möchtest du die Änderungen speichern?",
   "DOCUMENT_MODIFIED_SAVE": "Das Bild wurde verändert. Möchtest du die Änderungen speichern?",
-  "SESSION_UNSAVED_DATA": "{0} mit ungespeicherten Dateien. Bist du sicher?",
   "PROJECT_MAINTAINERS": "Projektverwalter",
   "PROJECT_MAINTAINERS": "Projektverwalter",
   "OTHER_AWESOME_CONTRIBUTORS": "Und andere tolle Mitwirkende",
   "OTHER_AWESOME_CONTRIBUTORS": "Und andere tolle Mitwirkende",
   "HELP": "Hilfe",
   "HELP": "Hilfe",
@@ -482,7 +473,6 @@
   "TOGGLE_VERTICAL_SYMMETRY": "Vertikale Symmetrie umschalten",
   "TOGGLE_VERTICAL_SYMMETRY": "Vertikale Symmetrie umschalten",
   "TOGGLE_HORIZONTAL_SYMMETRY": "Horizontale Symmetrie umschalten",
   "TOGGLE_HORIZONTAL_SYMMETRY": "Horizontale Symmetrie umschalten",
   "RESET_VIEWPORT": "Anzeigebereich zurücksetzten",
   "RESET_VIEWPORT": "Anzeigebereich zurücksetzten",
-  "VIEWPORT_SETTINGS": "Anzeigebereich Einstellungen",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Klicke und bewege die Maus um alle Pixel in den Ausgewählten Ebenen zu bewegen.",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Klicke und bewege die Maus um alle Pixel in den Ausgewählten Ebenen zu bewegen.",
   "CTRL_KEY": "Strg",
   "CTRL_KEY": "Strg",
   "SHIFT_KEY": "Shift",
   "SHIFT_KEY": "Shift",
@@ -507,7 +497,6 @@
   "SECURITY_ERROR_MSG": "Keine Berechtigungen um an dem Ort zu speichern.",
   "SECURITY_ERROR_MSG": "Keine Berechtigungen um an dem Ort zu speichern.",
   "IO_ERROR": "IO Fehler",
   "IO_ERROR": "IO Fehler",
   "IO_ERROR_MSG": "Fehler beim schreiben der Datei.",
   "IO_ERROR_MSG": "Fehler beim schreiben der Datei.",
-  "FAILED_ASSOCIATE_PIXI": "Fehler beim assoziieren von .pixi mit PixiEditor.",
   "COULD_NOT_SAVE_PALETTE": "Es gab einen Fehler beim speichern der Palette.",
   "COULD_NOT_SAVE_PALETTE": "Es gab einen Fehler beim speichern der Palette.",
   "NO_COLORS_TO_SAVE": "Es gibt keine Farben zum speichern.",
   "NO_COLORS_TO_SAVE": "Es gibt keine Farben zum speichern.",
   "SINGLE_LAYER": "Ausgewählte Ebene",
   "SINGLE_LAYER": "Ausgewählte Ebene",
@@ -547,7 +536,6 @@
   "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Klicke um eine Farbe von der Referenzebene zu picken.",
   "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Klicke um eine Farbe von der Referenzebene zu picken.",
   "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Klicke um eine Farbe von der Leinwand zu picken.",
   "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Klicke um eine Farbe von der Leinwand zu picken.",
   "LOCALIZATION_DEBUG_WINDOW_TITLE": "Lokalisierungs Debug Fenster",
   "LOCALIZATION_DEBUG_WINDOW_TITLE": "Lokalisierungs Debug Fenster",
-  "COMMAND_DEBUG_WINDOW_TITLE": "Command Debug Fenster",
   "SHORTCUTS_TITLE": "Tastenkürzel",
   "SHORTCUTS_TITLE": "Tastenkürzel",
   "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Ziehe die griffe um zu skalieren. Halte Strg and ziehe einen Griff um ihn frei zu bewegen. Halte Shift um proportional zu skalieren. Halte Alt und ziehe seitliche Griffe um zu kippen. Ziehe außerhalb der Griffe um zu rotieren.",
   "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Ziehe die griffe um zu skalieren. Halte Strg and ziehe einen Griff um ihn frei zu bewegen. Halte Shift um proportional zu skalieren. Halte Alt und ziehe seitliche Griffe um zu kippen. Ziehe außerhalb der Griffe um zu rotieren.",
   "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Ziehe die griffe um zu skalieren. Halte Shift um proportional zu skalieren. Halte Alt und ziehe seitliche Griffe um zu kippen. Ziehe außerhalb der Griffe um zu rotieren.",
   "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Ziehe die griffe um zu skalieren. Halte Shift um proportional zu skalieren. Halte Alt und ziehe seitliche Griffe um zu kippen. Ziehe außerhalb der Griffe um zu rotieren.",
@@ -662,7 +650,6 @@
   "BUILD_ID": "Build ID: {0}",
   "BUILD_ID": "Build ID: {0}",
   "ERASE_BLEND_MODE": "Radieren",
   "ERASE_BLEND_MODE": "Radieren",
   "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "Matrix-Transformationsfilter",
   "COLOR_MATRIX_TRANSFORM_FILTER_NODE": "Matrix-Transformationsfilter",
-  "MODIFY_IMAGE_PAIR_NODE": "Bild bearbeiten",
   "WITHOUT_FILTERS": "Ohne Filter",
   "WITHOUT_FILTERS": "Ohne Filter",
   "RAW_LAYER_OUTPUT": "Ohne alles",
   "RAW_LAYER_OUTPUT": "Ohne alles",
   "POND_EXAMPLE": "Teich",
   "POND_EXAMPLE": "Teich",
@@ -670,7 +657,6 @@
   "OUTLINE_EXAMPLE": "Automatische Kontur",
   "OUTLINE_EXAMPLE": "Automatische Kontur",
   "BETA_ANIMATIONS": "Animationen",
   "BETA_ANIMATIONS": "Animationen",
   "SLIME_EXAMPLE": "Animierter Schleim",
   "SLIME_EXAMPLE": "Animierter Schleim",
-  "SHOW_ALL_EXAMPLES": "Alle anzeigen",
   "APPLY_FILTER_NODE": "Filter anwenden",
   "APPLY_FILTER_NODE": "Filter anwenden",
   "FILTER": "Filter",
   "FILTER": "Filter",
   "LERP_NODE": "Lerp",
   "LERP_NODE": "Lerp",
@@ -687,7 +673,6 @@
   "POINTS": "Punkte",
   "POINTS": "Punkte",
   "MIN_DISTANCE": "Min. Distanz",
   "MIN_DISTANCE": "Min. Distanz",
   "MAX_POINTS": "Max. Distanz",
   "MAX_POINTS": "Max. Distanz",
-  "PROBABILITY": "Wahrscheinlichkeit",
   "DISTRIBUTE_POINTS": "Punkte verteilen",
   "DISTRIBUTE_POINTS": "Punkte verteilen",
   "REMOVE_CLOSE_POINTS": "Benachbarte Punkte entfernen",
   "REMOVE_CLOSE_POINTS": "Benachbarte Punkte entfernen",
   "MODE": "Modus",
   "MODE": "Modus",
@@ -862,7 +847,6 @@
   "INPUT_MATRIX": "Matrix",
   "INPUT_MATRIX": "Matrix",
   "OUTPUT_MATRIX": "Matrix Ergebnis",
   "OUTPUT_MATRIX": "Matrix Ergebnis",
   "CENTER": "Mittelpunkt",
   "CENTER": "Mittelpunkt",
-  "CONTENT_OFFSET": "Inhalt Abstand",
   "CANVAS_POSITION": "Leinwand Position",
   "CANVAS_POSITION": "Leinwand Position",
   "CENTER_POSITION": "Zentrum",
   "CENTER_POSITION": "Zentrum",
   "TILE_MODE_X": "Spiegelmodus X",
   "TILE_MODE_X": "Spiegelmodus X",
@@ -871,7 +855,6 @@
   "SKEW": "Verzerren",
   "SKEW": "Verzerren",
   "OFFSET_NODE": "Verschieben",
   "OFFSET_NODE": "Verschieben",
   "SKEW_NODE": "Verzerren",
   "SKEW_NODE": "Verzerren",
-  "ROTATION_NODE": "Rotieren",
   "SCALE_NODE": "Skalieren",
   "SCALE_NODE": "Skalieren",
   "ROTATE_NODE": "Rotieren",
   "ROTATE_NODE": "Rotieren",
   "TRANSFORM_NODE": "Transformation",
   "TRANSFORM_NODE": "Transformation",
@@ -895,8 +878,6 @@
   "CONTRAST_VALUE": "Kontrast",
   "CONTRAST_VALUE": "Kontrast",
   "TEMPERATURE_VALUE": "Temperatur",
   "TEMPERATURE_VALUE": "Temperatur",
   "TINT_VALUE": "Ton",
   "TINT_VALUE": "Ton",
-  "FAILED_DOWNLOADING_UPDATE_TITLE": "Fehler beim herunterladen",
-  "FAILED_DOWNLOADING_UPDATE": "Fehler beim herunterladen des Updates. Versuche es später erneut.",
   "DELETE_SELECTED": "Ausgewählte Löschen",
   "DELETE_SELECTED": "Ausgewählte Löschen",
   "DELETE_SELECTED_DESCRIPTIVE": "Ausgewählte Elemente löschen (ebenen, pixel, usw...)",
   "DELETE_SELECTED_DESCRIPTIVE": "Ausgewählte Elemente löschen (ebenen, pixel, usw...)",
   "GRIDLINES_SIZE": "Gittergröße",
   "GRIDLINES_SIZE": "Gittergröße",
@@ -945,10 +926,6 @@
   "IN_BOUNCE_EASING_TYPE": "Nachgeben bei Sprung",
   "IN_BOUNCE_EASING_TYPE": "Nachgeben bei Sprung",
   "OUT_BOUNCE_EASING_TYPE": "Nachgeben von Sprung",
   "OUT_BOUNCE_EASING_TYPE": "Nachgeben von Sprung",
   "IN_OUT_BOUNCE_EASING_TYPE": "Leichte Ein/aussteigen Sprung",
   "IN_OUT_BOUNCE_EASING_TYPE": "Leichte Ein/aussteigen Sprung",
-  "CLAMP_SHADER_TILE_NODE": "Begrenzen",
-  "REPEAT_SHADER_TILE_NODE": "Wiederholen",
-  "MIRROR_SHADER_TILE_NODE": "Spiegeln",
-  "DECAL_SHADER_TILE_NODE": "Abbilden",
   "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
   "R_G_B_COMBINE_SEPARATE_COLOR_MODE": "RGB",
   "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
   "H_S_V_COMBINE_SEPARATE_COLOR_MODE": "HSV",
   "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
   "H_S_L_COMBINE_SEPARATE_COLOR_MODE": "HSL",
@@ -982,7 +959,6 @@
   "UP_TO_DATE_UNKNOWN": "Konnte nicht nach Updates suchen",
   "UP_TO_DATE_UNKNOWN": "Konnte nicht nach Updates suchen",
   "UP_TO_DATE": "PixiEditor Version ist aktuell",
   "UP_TO_DATE": "PixiEditor Version ist aktuell",
   "UPDATE_AVAILABLE": "Update {0} ist verfügbar",
   "UPDATE_AVAILABLE": "Update {0} ist verfügbar",
-  "CHECKING_UPDATES": "Überprüfe auf Updates...",
   "UPDATE_FAILED_DOWNLOAD": "Fehler beim Herunterladen des Updates",
   "UPDATE_FAILED_DOWNLOAD": "Fehler beim Herunterladen des Updates",
   "UPDATE_READY_TO_INSTALL": "Update ist bereit. Auf {0} wechseln?",
   "UPDATE_READY_TO_INSTALL": "Update ist bereit. Auf {0} wechseln?",
   "SWITCH_TO_NEW_VERSION": "Wechseln",
   "SWITCH_TO_NEW_VERSION": "Wechseln",
@@ -1032,7 +1008,6 @@
   "ERROR_GRAPH": "Knotenkonstellation hat einen Fehler produziert. Behebe ihn in der Knotenansicht",
   "ERROR_GRAPH": "Knotenkonstellation hat einen Fehler produziert. Behebe ihn in der Knotenansicht",
   "COLOR_MATRIX_FILTER_NODE": "Farbmatrix FIlter",
   "COLOR_MATRIX_FILTER_NODE": "Farbmatrix FIlter",
   "WORKSPACE": "Arbeitsbereich",
   "WORKSPACE": "Arbeitsbereich",
-  "EXPORT_ZONE_NODE": "Zone exportieren",
   "IS_DEFAULT_EXPORT": "Standard für Exportieren",
   "IS_DEFAULT_EXPORT": "Standard für Exportieren",
   "EXPORT_OUTPUT": "Knotenergebnis",
   "EXPORT_OUTPUT": "Knotenergebnis",
   "RENDER_OUTPUT_SIZE": "Exportierte Größe",
   "RENDER_OUTPUT_SIZE": "Exportierte Größe",
@@ -1071,7 +1046,6 @@
   "CONNECTION_TIMEOUT": "Zeitüberschreitung bei der Verbindung. Bitte versuche es erneut.",
   "CONNECTION_TIMEOUT": "Zeitüberschreitung bei der Verbindung. Bitte versuche es erneut.",
   "OPEN_ACCOUNT_WINDOW": "Account verwalten",
   "OPEN_ACCOUNT_WINDOW": "Account verwalten",
   "INSTALL": "Installieren",
   "INSTALL": "Installieren",
-  "MANAGE_ACCOUNT": "Verwalten",
   "OWNED_PRODUCTS": "Meine Inhalte",
   "OWNED_PRODUCTS": "Meine Inhalte",
   "INSTALLING": "Installiere",
   "INSTALLING": "Installiere",
   "INSTALLED": "Installiert",
   "INSTALLED": "Installiert",

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

@@ -1133,5 +1133,37 @@
   "TWIST": "Twist",
   "TWIST": "Twist",
     "TILT": "Tilt",
     "TILT": "Tilt",
   "POINTER_INFO_NODE": "Pointer Info",
   "POINTER_INFO_NODE": "Pointer Info",
-  "ALLOW_SAMPLE_STACKING": "Allow sample stacking"
+  "ALLOW_SAMPLE_STACKING": "Allow sample stacking",
+  "SLICE_TEXT_NODE": "Slice text",
+  "INDEX_START_AT": "Start at",
+  "CHARACTER_POSITION_NODE": "Character Position",
+  "FIRST_POSITION": "First position",
+  "LAST_POSITION": "Last position",
+  "MATCH_CASE": "Match case",
+  "SEARCH_TEXT": "Search text",
+  "TEXT_INFO_NODE": "Text info",
+  "TEXT_LENGTH": "Length",
+  "TEXT_LINE_COUNT": "Line count",
+  "TEXT_SLICE_USE_LENGTH": "Use length",
+  "TOGGLE_TINTING_SELECTION": "Toggle selection tinting",
+  "TOGGLE_TINTING_SELECTION_DESCRIPTIVE": "Toggle selection tinting",
+  "TINT_SELECTION": "Selection tinting",
+  "PAINT_BRUSH_SHAPE_CIRCLE": "Circle",
+  "PAINT_BRUSH_SHAPE_SQUARE": "Square",
+  "BRIGHTNESS_MODE_DEFAULT": "Default",
+  "BRIGHTNESS_MODE_REPEAT": "Repeat",
+  "ROUND_STROKE_CAP": "Round",
+  "BUTT_STROKE_CAP": "Butt",
+  "SQUARE_STROKE_CAP": "Square",
+  "ROUND_STROKE_JOIN": "Round",
+  "MITER_STROKE_JOIN": "Miter",
+  "BEVEL_STROKE_JOIN": "Bevel",
+  "TOGGLE_FULLSCREEN": "Toggle Fullscreen",
+  "TOGGLE_FULLSCREEN_DESCRIPTIVE": "Enter or Exit Fullscreen Mode",
+  "SHORTCUTS_IMPORTED_WITH_ERRORS": "Importing shortcuts finished with errors.\nSome shortcuts were not recognized and have been skipped.",
+  "OPEN_ERROR_LOG": "Open error log",
+  "POSTERIZATION_NODE": "Posterize",
+  "LEVELS": "Levels",
+  "RGB_POSTERIZATION_MODE": "RGB",
+  "LUMINANCE_POSTERIZATION_MODE": "Luminance"
 }
 }

+ 160 - 165
src/PixiEditor/Data/Localization/Languages/es.json

@@ -1,33 +1,33 @@
 {
 {
-  "RECENT_FILES": "Archivo Reciente",
-  "OPEN_FILE": "Abrir Archivo",
+  "RECENT_FILES": "Archivos recientes",
+  "OPEN_FILE": "Abrir archivo",
   "NEW_FILE": "Nuevo",
   "NEW_FILE": "Nuevo",
-  "RECENT_EMPTY_TEXT": "Demasiado espacio vacio",
+  "RECENT_EMPTY_TEXT": "Bastante vacío aquí",
   "LANGUAGE": "Idioma",
   "LANGUAGE": "Idioma",
   "GENERAL": "General",
   "GENERAL": "General",
   "DISCORD": "Discord",
   "DISCORD": "Discord",
-  "KEY_BINDINGS": "Fijaciones de teclas",
-  "MISC": "Otros",
-  "SHOW_STARTUP_WINDOW": "Abrir Al Iniciar Windows",
+  "KEY_BINDINGS": "Atajos del teclado",
+  "MISC": "Misceláneo",
+  "SHOW_STARTUP_WINDOW": "Mostrar ventana de bienvenida",
   "RECENT_FILE_LENGTH": "Longitud de la lista de archivos reciente",
   "RECENT_FILE_LENGTH": "Longitud de la lista de archivos reciente",
-  "RECENT_FILE_LENGTH_TOOLTIP": "Cuántos documentos se muestran en Archivo > Recientes. Por defecto: 8",
-  "DEFAULT_NEW_SIZE": "Nuevo archivo por defecto",
-  "WIDTH": "Anchura",
-  "HEIGHT": "Altura",
+  "RECENT_FILE_LENGTH_TOOLTIP": "Cuántos documentos se muestran en Archivo > Recientes. Predeterminado: 8",
+  "DEFAULT_NEW_SIZE": "Tamaño de imagen predeterminado",
+  "WIDTH": "Ancho",
+  "HEIGHT": "Alto",
   "TOOLS": "Herramientas",
   "TOOLS": "Herramientas",
   "ENABLE_SHARED_TOOLBAR": "Activar la barra de herramientas compartida",
   "ENABLE_SHARED_TOOLBAR": "Activar la barra de herramientas compartida",
-  "AUTOMATIC_UPDATES": "Actualizaciones Automáticas",
+  "AUTOMATIC_UPDATES": "Actualizaciones automáticas",
   "CHECK_FOR_UPDATES": "Buscar actualizaciones al iniciar",
   "CHECK_FOR_UPDATES": "Buscar actualizaciones al iniciar",
-  "UPDATE_STREAM": "Actualizar stream",
-  "UPDATE_CHANNEL_HELP_TOOLTIP": "Los canales de actualización sólo se pueden cambiar en la versión independiente (descargada de https://pixieditor.net).\nLas versiones de Steam y Microsoft Store gestionan las actualizaciones por separado.",
-  "DEBUG": "Debug",
-  "ENABLE_DEBUG_MODE": "Activar modo Debug",
-  "OPEN_CRASH_REPORTS_DIR": "Abrir lista de reportes",
+  "UPDATE_STREAM": "Frecuencia de actualizaciones",
+  "UPDATE_CHANNEL_HELP_TOOLTIP": "Las actualizaciones sólo se pueden gestionar manualmente en la versión descargada por el sitio del programa (https://pixieditor.net).\nSteam y la tienda de Microsoft gestionan las actualizaciones por separado.",
+  "DEBUG": "Depuración",
+  "ENABLE_DEBUG_MODE": "Activar modo de depuración",
+  "OPEN_CRASH_REPORTS_DIR": "Abrir carpeta de reportes de colapsos",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "DISCORD_RICH_PRESENCE": "Rich Presence",
   "ENABLED": "Activado",
   "ENABLED": "Activado",
-  "SHOW_IMAGE_NAME": "Mostrar nombre del archivo",
-  "SHOW_IMAGE_SIZE": "Mostrar tamaño del archivo",
-  "SHOW_LAYER_COUNT": "Mostrar recuento de capas",
+  "SHOW_IMAGE_NAME": "Mostrar nombre de la imagen",
+  "SHOW_IMAGE_SIZE": "Mostrar tamaño de la imagen",
+  "SHOW_LAYER_COUNT": "Mostrar cantidad de capas",
   "FILE": "Archivo",
   "FILE": "Archivo",
   "RECENT": "Reciente",
   "RECENT": "Reciente",
   "OPEN": "Abrir",
   "OPEN": "Abrir",
@@ -38,119 +38,113 @@
   "EXIT": "Salir",
   "EXIT": "Salir",
   "PERCENTAGE": "Porcentaje",
   "PERCENTAGE": "Porcentaje",
   "ABSOLUTE": "Absoluto",
   "ABSOLUTE": "Absoluto",
-  "PRESERVE_ASPECT_RATIO": "Conservar la relación de aspecto",
-  "ANCHOR_POINT": "Punto de anclaje",
-  "RESIZE_IMAGE": "Cambiar el tamaño de la imagen",
-  "RESIZE": "Cambie el tamaño de",
-  "DOCUMENTATION": "Documentacion",
-  "WEBSITE": "Pagina Web",
-  "OPEN_WEBSITE": "Abrir Pagina Web",
+  "PRESERVE_ASPECT_RATIO": "Preservar la relación de aspecto",
+  "ANCHOR_POINT": "Anclaje",
+  "RESIZE_IMAGE": "Reescalar la imagen",
+  "RESIZE": "Confirmar",
+  "DOCUMENTATION": "Documentación",
+  "WEBSITE": "Pagina web",
+  "OPEN_WEBSITE": "Abrir Pagina web",
   "REPOSITORY": "Repositorio",
   "REPOSITORY": "Repositorio",
   "OPEN_REPOSITORY": "Abrir Repositorio",
   "OPEN_REPOSITORY": "Abrir Repositorio",
   "LICENSE": "Licencia",
   "LICENSE": "Licencia",
-  "OPEN_LICENSE": "Abrir Licencia",
+  "OPEN_LICENSE": "Abrir licencia",
   "THIRD_PARTY_LICENSES": "Licencias de terceros",
   "THIRD_PARTY_LICENSES": "Licencias de terceros",
   "OPEN_THIRD_PARTY_LICENSES": "Abrir licencias de terceros",
   "OPEN_THIRD_PARTY_LICENSES": "Abrir licencias de terceros",
   "APPLY_TRANSFORM": "Aplicar transformacion",
   "APPLY_TRANSFORM": "Aplicar transformacion",
-  "INCREASE_TOOL_SIZE": "Aumentar el tamaño de la herramienta",
-  "DECREASE_TOOL_SIZE": "Disminuir el tamaño de la herramienta",
-  "DOWNLOADING_UPDATE": "Instalando actualizacion...",
-  "UPDATE_READY": "Actualización listo para instalar. ¿Desea instalarlo ahora?",
-  "NEW_UPDATE": "Nueva actualizacion",
-  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "No se ha podido actualizar sin privilegios de administrador. Por favor, ejecute PixiEditor como administrador.",
+  "INCREASE_TOOL_SIZE": "Agrandar la herramienta",
+  "DECREASE_TOOL_SIZE": "Desagrandar la herramienta",
+  "DOWNLOADING_UPDATE": "Descargando actualización...",
+  "UPDATE_READY": "La actualización está lista para instalar. ¿Quieres instalarla ahora?",
+  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "No se puede actualizar sin privilegios de administrador. Ejecuta PixiEditor como administrador.",
   "INSUFFICIENT_PERMISSIONS": "Permisos insuficientes",
   "INSUFFICIENT_PERMISSIONS": "Permisos insuficientes",
-  "UPDATE_CHECK_FAILED": "Error en la comprobación de la actualización",
-  "COULD_NOT_CHECK_FOR_UPDATES": "No se ha podido comprobar si hay una actualización disponible.",
-  "VERSION": "Version {0}",
+  "VERSION": "Versión {0}",
   "OPEN_TEMP_DIR": "Abrir directorio temporal",
   "OPEN_TEMP_DIR": "Abrir directorio temporal",
-  "OPEN_LOCAL_APPDATA_DIR": "Abra el directorio local AppData",
-  "OPEN_ROAMING_APPDATA_DIR": "Abrir el directorio Roaming AppData",
+  "OPEN_LOCAL_APPDATA_DIR": "Abra la carpeta Local AppData",
+  "OPEN_ROAMING_APPDATA_DIR": "Abrir la carpeta Roaming AppData",
   "OPEN_INSTALLATION_DIR": "Abrir el directorio de instalación",
   "OPEN_INSTALLATION_DIR": "Abrir el directorio de instalación",
-  "DUMP_ALL_COMMANDS": "Volcar todos los comandos",
-  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Volcar todos los comandos a un archivo de texto",
-  "CRASH": "Error",
-  "CRASH_APP": "Error en la aplicacion",
-  "DELETE_USR_PREFS": "Eliminar las preferencias del usuario (Roaming AppData)",
+  "DUMP_ALL_COMMANDS": "Exportar todos los comandos",
+  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Exportar todos los comandos a un archivo de texto",
+  "CRASH": "Forzar cierre",
+  "CRASH_APP": "Forzar el cierre de la aplicación",
+  "DELETE_USR_PREFS": "Eliminar las preferencias de usuario (Roaming AppData)",
   "DELETE_SHORTCUT_FILE": "Eliminar el archivo de acceso directo (Roaming AppData)",
   "DELETE_SHORTCUT_FILE": "Eliminar el archivo de acceso directo (Roaming AppData)",
   "DELETE_EDITOR_DATA": "Eliminar datos del editor (Local AppData)",
   "DELETE_EDITOR_DATA": "Eliminar datos del editor (Local AppData)",
-  "GENERATE_KEY_BINDINGS_TEMPLATE": "Generar plantilla de enlaces de teclas",
-  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generar plantilla json de enlaces de claves",
-  "VALIDATE_SHORTCUT_MAP": "Validar dirección de accesos directos",
-  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Validados dirección de accesos directos",
-  "VALIDATION_KEYS_NOTICE_DIALOG": "Llaves vacías: {0}\nComandos desconocidos: {1}",
+  "GENERATE_KEY_BINDINGS_TEMPLATE": "Generar plantilla de atajos del teclado",
+  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Generar plantilla de atajos de teclado en formato JSON",
+  "VALIDATE_SHORTCUT_MAP": "Validar mapa de atajos del teclado",
+  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Valida los atajos del teclado",
+  "VALIDATION_KEYS_NOTICE_DIALOG": "Claves vacías: {0}\nComandos desconocidos: {1}",
   "RESULT": "Resultado",
   "RESULT": "Resultado",
-  "CLEAR_RECENT_DOCUMENTS": "Limpiar archivos recientes",
-  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Limpiar documentos recientemente abiertos",
-  "OPEN_CMD_DEBUG_WINDOW": "Abrir caja de comandos debug de windows",
+  "CLEAR_RECENT_DOCUMENTS": "Limpiar documentos recientes",
+  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Limpiar documentos abiertos recientemente",
+  "OPEN_CMD_DEBUG_WINDOW": "Abrir ventana de comandos de depuración",
   "PATH_DOES_NOT_EXIST": "{0} no existe.",
   "PATH_DOES_NOT_EXIST": "{0} no existe.",
   "LOCATION_DOES_NOT_EXIST": "La ubicación no existe.",
   "LOCATION_DOES_NOT_EXIST": "La ubicación no existe.",
   "FILE_NOT_FOUND": "Archivo no encontrado.",
   "FILE_NOT_FOUND": "Archivo no encontrado.",
   "ARE_YOU_SURE": "¿Estas seguro?",
   "ARE_YOU_SURE": "¿Estas seguro?",
   "ARE_YOU_SURE_PATH_FULL_PATH": "¿Está seguro de que desea borrar {0}?\nEstos datos se perderán para todas las instalaciones.\n(Ruta completa: {1})",
   "ARE_YOU_SURE_PATH_FULL_PATH": "¿Está seguro de que desea borrar {0}?\nEstos datos se perderán para todas las instalaciones.\n(Ruta completa: {1})",
-  "FAILED_TO_OPEN_FILE": "Error al abrir el archivo",
-  "OLD_FILE_FORMAT": "Formato de archivo viejo",
+  "FAILED_TO_OPEN_FILE": "No se pudo abrir el archivo",
+  "OLD_FILE_FORMAT": "Formato de archivo antiguo",
   "OLD_FILE_FORMAT_DESCRIPTION": "Este archivo .pixi utiliza el formato antiguo,\nque ya no es compatible y no puede abrirse.",
   "OLD_FILE_FORMAT_DESCRIPTION": "Este archivo .pixi utiliza el formato antiguo,\nque ya no es compatible y no puede abrirse.",
   "NOTHING_FOUND": "Nada encontrado",
   "NOTHING_FOUND": "Nada encontrado",
   "EXPORT": "Exportar",
   "EXPORT": "Exportar",
   "EXPORT_IMAGE": "Exportar imagen",
   "EXPORT_IMAGE": "Exportar imagen",
   "IMPORT": "Importar",
   "IMPORT": "Importar",
-  "SHORTCUT_TEMPLATES": "Plantillas de acceso directo",
-  "RESET_ALL": "Reiniciar todo",
+  "SHORTCUT_TEMPLATES": "Plantillas de atajos del teclado",
+  "RESET_ALL": "Restaurar todos",
   "LAYER": "Capa",
   "LAYER": "Capa",
-  "LAYER_DELETE_SELECTED": "Borrar capa/carpeta activa",
-  "LAYER_DELETE_SELECTED_DESCRIPTIVE": "Borrar capa o carpeta activa",
   "LAYER_DELETE_ALL_SELECTED": "Eliminar todas las capas/carpetas seleccionadas",
   "LAYER_DELETE_ALL_SELECTED": "Eliminar todas las capas/carpetas seleccionadas",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Eliminar todas las capas y/o carpetas seleccionadas",
   "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Eliminar todas las capas y/o carpetas seleccionadas",
-  "DELETE_SELECTED_PIXELS": "Borrar píxeles seleccionados",
   "NEW_FOLDER": "Nueva carpeta",
   "NEW_FOLDER": "Nueva carpeta",
   "CREATE_NEW_FOLDER": "Crear nueva carpeta",
   "CREATE_NEW_FOLDER": "Crear nueva carpeta",
   "NEW_LAYER": "Nueva capa",
   "NEW_LAYER": "Nueva capa",
   "CREATE_NEW_LAYER": "Crear nueva capa",
   "CREATE_NEW_LAYER": "Crear nueva capa",
-  "NEW_IMAGE": "Imagen nueva",
-  "CREATE_NEW_IMAGE": "Crear imagen nueva",
+  "NEW_IMAGE": "Nueva imagen",
+  "CREATE_NEW_IMAGE": "Crear nueva imagen",
   "SAVE": "Guardar",
   "SAVE": "Guardar",
   "SAVE_AS": "Guardar como...",
   "SAVE_AS": "Guardar como...",
   "IMAGE": "Imagen",
   "IMAGE": "Imagen",
   "SAVE_IMAGE": "Guardar imagen",
   "SAVE_IMAGE": "Guardar imagen",
-  "SAVE_IMAGE_AS": "Guardar imagen como nuevo",
+  "SAVE_IMAGE_AS": "Guardar imagen como nuevo archivo",
   "DUPLICATE": "Duplicar",
   "DUPLICATE": "Duplicar",
   "DUPLICATE_SELECTED_LAYER": "Duplicar capa selecionada",
   "DUPLICATE_SELECTED_LAYER": "Duplicar capa selecionada",
-  "CREATE_MASK": "Crear mascara",
-  "DELETE_MASK": "Borrar mascara",
-  "TOGGLE_MASK": "Máscara de conmutación",
-  "APPLY_MASK": "Aplicar mascara",
+  "CREATE_MASK": "Crear máscara",
+  "DELETE_MASK": "Borrar máscara",
+  "TOGGLE_MASK": "Alternar máscara",
+  "APPLY_MASK": "Aplicar máscara",
   "TOGGLE_VISIBILITY": "Alternar visibilidad",
   "TOGGLE_VISIBILITY": "Alternar visibilidad",
-  "MOVE_MEMBER_UP": "Desplazar al miembro hacia arriba",
-  "MOVE_MEMBER_UP_DESCRIPTIVE": "Mover hacia arriba la capa o carpeta seleccionada",
-  "MOVE_MEMBER_DOWN": "Desplazar el miembro hacia abajo",
-  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Mover hacia abajo la capa o carpeta seleccionada",
-  "MERGE_ALL_SELECTED_LAYERS": "Fusionar todas las capas seleccionadas",
-  "MERGE_WITH_ABOVE": "Fusionar la capa seleccionada con la anterior",
-  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Fusionar la capa seleccionada con la superior",
-  "MERGE_WITH_BELOW": "Fusionar la capa seleccionada con la de abajo",
-  "MERGE_WITH_BELOW_DESCRIPTIVE": "Fusionar la capa seleccionada con la inferior",
+  "MOVE_MEMBER_UP": "Mover miembro hacia arriba",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "Mover la capa o carpeta seleccionada hacia arriba",
+  "MOVE_MEMBER_DOWN": "Desplazar miembro hacia abajo",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Mover la capa o carpeta seleccionada hacia abajo",
+  "MERGE_ALL_SELECTED_LAYERS": "Combinar todas las capas seleccionadas",
+  "MERGE_WITH_ABOVE": "Combinar la capa seleccionada con la de arriba",
+  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Combinar la capa seleccionada con la que esté por encima",
+  "MERGE_WITH_BELOW": "Combinar la capa seleccionada con la de abajo",
+  "MERGE_WITH_BELOW_DESCRIPTIVE": "Combinar la capa seleccionada con la inferior",
   "ADD_REFERENCE_LAYER": "Añadir capa de referencia",
   "ADD_REFERENCE_LAYER": "Añadir capa de referencia",
   "DELETE_REFERENCE_LAYER": "Eliminar capa de referencia",
   "DELETE_REFERENCE_LAYER": "Eliminar capa de referencia",
   "TRANSFORM_REFERENCE_LAYER": "Transformar capa de referencia",
   "TRANSFORM_REFERENCE_LAYER": "Transformar capa de referencia",
-  "TOGGLE_REFERENCE_LAYER_POS": "Conmutar la posición de la capa de referencia",
-  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Conmutar la capa de referencia entre la superior o la inferior",
+  "TOGGLE_REFERENCE_LAYER_POS": "Alternar la posición de la capa de referencia",
+  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Configurar el si la capa de referencia se encuentra arriba o abajo de todo",
   "RESET_REFERENCE_LAYER_POS": "Restablecer la posición de la capa de referencia",
   "RESET_REFERENCE_LAYER_POS": "Restablecer la posición de la capa de referencia",
-  "CLIP_CANVAS": "Adjuntar Canvas",
-  "FLIP_IMG_VERTICALLY": "Voltear imagen verticalmente",
-  "FLIP_IMG_HORIZONTALLY": "Voltear imagen horizontalmente",
-  "FLIP_LAYERS_VERTICALLY": "Voltear verticalmente las capas seleccionadas",
-  "FLIP_LAYERS_HORIZONTALLY": "Voltear horizontalmente las capas seleccionadas",
-  "ROT_IMG_90": "Girar la imagen 90 grados",
-  "ROT_IMG_180": "Girar la imagen 180 grados",
-  "ROT_IMG_-90": "Girar la imagen -90 grados",
-  "ROT_LAYERS_90": "Girar 90 grados las capas seleccionadas",
-  "ROT_LAYERS_180": "Girar 180 grados las capas seleccionadas",
-  "ROT_LAYERS_-90": "Girar -90 grados las capas seleccionadas",
-  "TOGGLE_VERT_SYMMETRY_AXIS": "Conmutar el eje de simetría vertical",
-  "TOGGLE_HOR_SYMMETRY_AXIS": "Conmutar el eje de simetría horizontal",
-  "RESIZE_DOCUMENT": "Cambiar el tamaño del documento",
-  "RESIZE_CANVAS": "Cambiar el tamaño del canvas",
-  "CENTER_CONTENT": "Contenido central",
+  "CLIP_CANVAS": "Recortar lienzo",
+  "FLIP_IMG_VERTICALLY": "Espejar imagen verticalmente",
+  "FLIP_IMG_HORIZONTALLY": "Espejar imagen horizontalmente",
+  "FLIP_LAYERS_VERTICALLY": "Espejar verticalmente las capas seleccionadas",
+  "FLIP_LAYERS_HORIZONTALLY": "Espejar horizontalmente las capas seleccionadas",
+  "ROT_IMG_90": "Rotar la imagen 90 grados",
+  "ROT_IMG_180": "Rotar la imagen 180 grados",
+  "ROT_IMG_-90": "Rotar la imagen -90 grados",
+  "ROT_LAYERS_90": "Rotar las capas seleccionadas 90 grados",
+  "ROT_LAYERS_180": "Rotar las capas seleccionadas 180 grados",
+  "ROT_LAYERS_-90": "Rotar las capas seleccionadas -90 grados",
+  "TOGGLE_VERT_SYMMETRY_AXIS": "Activación del eje de simetría vertical",
+  "TOGGLE_HOR_SYMMETRY_AXIS": "Activación del eje de simetría horizontal",
+  "RESIZE_DOCUMENT": "Reescalar documento",
+  "RESIZE_CANVAS": "Reescalar lienzo",
+  "CENTER_CONTENT": "Centrar contenido",
   "CUT": "Cortar",
   "CUT": "Cortar",
   "CUT_DESCRIPTIVE": "Cortar el área o las capas seleccionadas",
   "CUT_DESCRIPTIVE": "Cortar el área o las capas seleccionadas",
   "PASTE": "Pegar",
   "PASTE": "Pegar",
@@ -167,20 +161,19 @@
   "COPY": "Copiar",
   "COPY": "Copiar",
   "COPY_DESCRIPTIVE": "Copiar al portapapeles",
   "COPY_DESCRIPTIVE": "Copiar al portapapeles",
   "COPY_COLOR_HEX": "Copiar color primario (HEX)",
   "COPY_COLOR_HEX": "Copiar color primario (HEX)",
-  "COPY_COLOR_HEX_DESCRIPTIVE": "Copiar color primario como código HEX",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "Copiar color primario como código hexadecimal",
   "COPY_COLOR_RGB": "Copiar color primario (RGB)",
   "COPY_COLOR_RGB": "Copiar color primario (RGB)",
   "COPY_COLOR_RGB_DESCRIPTIVE": "Copiar color primario como código RGB",
   "COPY_COLOR_RGB_DESCRIPTIVE": "Copiar color primario como código RGB",
   "COPY_COLOR_SECONDARY_HEX": "Copiar color secundario (HEX)",
   "COPY_COLOR_SECONDARY_HEX": "Copiar color secundario (HEX)",
-  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copiar color secundario como código HEX",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copiar color secundario como código hexadecimal",
   "COPY_COLOR_SECONDARY_RGB": "Copiar color secundario (RGB)",
   "COPY_COLOR_SECONDARY_RGB": "Copiar color secundario (RGB)",
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copiar color secundario como código RGB",
   "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copiar color secundario como código RGB",
   "PALETTE_COLORS": "Colores de la paleta",
   "PALETTE_COLORS": "Colores de la paleta",
-  "REPLACE_SECONDARY_BY_PRIMARY": "Sustituir el color secundario por el primario",
-  "REPLACE_SECONDARY_BY_PRIMARY_DESCRIPTIVE": "Sustituye el color secundario por el primario",
+  "REPLACE_SECONDARY_BY_PRIMARY": "Reemplazar el color secundario por el primario",
   "REPLACE_PRIMARY_BY_SECONDARY": "Sustituir el color primario por el secundario",
   "REPLACE_PRIMARY_BY_SECONDARY": "Sustituir el color primario por el secundario",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Sustituir el color primario por el secundario",
   "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Sustituir el color primario por el secundario",
   "OPEN_PALETTE_BROWSER": "Abrir el navegador de paletas",
   "OPEN_PALETTE_BROWSER": "Abrir el navegador de paletas",
-  "OVERWRITE_PALETTE_CONSENT": "La paleta '{0}' ya existe, ¿desea sobrescribirla?",
+  "OVERWRITE_PALETTE_CONSENT": "La paleta '{0}' ya existe, ¿deseas sobrescribirla?",
   "PALETTE_EXISTS": "La paleta ya existe",
   "PALETTE_EXISTS": "La paleta ya existe",
   "REPLACE_PALETTE_CONSENT": "¿Reemplazar la paleta actual por la seleccionada?",
   "REPLACE_PALETTE_CONSENT": "¿Reemplazar la paleta actual por la seleccionada?",
   "REPLACE_PALETTE": "Sustituir la paleta actual",
   "REPLACE_PALETTE": "Sustituir la paleta actual",
@@ -194,11 +187,11 @@
   "SELECT_COLOR_8": "Seleccionar color  8",
   "SELECT_COLOR_8": "Seleccionar color  8",
   "SELECT_COLOR_9": "Seleccionar color 9",
   "SELECT_COLOR_9": "Seleccionar color 9",
   "SELECT_COLOR_10": "Seleccionar color 10",
   "SELECT_COLOR_10": "Seleccionar color 10",
-  "SELECT_TOOL": "Seleccionar {0} Herramienta",
-  "SELECT_COLOR_1_DESCRIPTIVE": "Seleccione el primer color de la paleta",
-  "SELECT_COLOR_2_DESCRIPTIVE": "Seleccione el segundo color de la paleta",
-  "SELECT_COLOR_3_DESCRIPTIVE": "Seleccione el tercero color de la paleta",
-  "SELECT_COLOR_4_DESCRIPTIVE": "Seleccione el cuarto color de la paleta",
+  "SELECT_TOOL": "Seleccionar la herramienta de {0}",
+  "SELECT_COLOR_1_DESCRIPTIVE": "Seleccione el primer color en la paleta",
+  "SELECT_COLOR_2_DESCRIPTIVE": "Seleccione el segundo color en la paleta",
+  "SELECT_COLOR_3_DESCRIPTIVE": "Seleccione el tercero color en la paleta",
+  "SELECT_COLOR_4_DESCRIPTIVE": "Seleccione el cuarto color en la paleta",
   "SELECT_COLOR_5_DESCRIPTIVE": "Seleccione el quinto color de la paleta",
   "SELECT_COLOR_5_DESCRIPTIVE": "Seleccione el quinto color de la paleta",
   "SELECT_COLOR_6_DESCRIPTIVE": "Seleccione el sexto color de la paleta",
   "SELECT_COLOR_6_DESCRIPTIVE": "Seleccione el sexto color de la paleta",
   "SELECT_COLOR_7_DESCRIPTIVE": "Seleccione el séptimo color de la paleta",
   "SELECT_COLOR_7_DESCRIPTIVE": "Seleccione el séptimo color de la paleta",
@@ -213,92 +206,92 @@
   "SELECT": "Seleccionar",
   "SELECT": "Seleccionar",
   "DESELECT": "Deseleccionar",
   "DESELECT": "Deseleccionar",
   "INVERT": "Invertir",
   "INVERT": "Invertir",
-  "SELECTION": "Seleccionar",
+  "SELECTION": "Selección",
   "SELECT_ALL": "Seleccionar todo",
   "SELECT_ALL": "Seleccionar todo",
-  "SELECT_ALL_DESCRIPTIVE": "Seleccionar todo",
-  "CLEAR_SELECTION": "Limpiar seleccionado",
-  "INVERT_SELECTION": "Invertir seleccionado",
-  "INVERT_SELECTION_DESCRIPTIVE": "Invertir zona seleccionada",
+  "SELECT_ALL_DESCRIPTIVE": "Seleccionar todos los pixeles de la imagen o capa activa",
+  "CLEAR_SELECTION": "Anular selección",
+  "INVERT_SELECTION": "Invertir selección",
+  "INVERT_SELECTION_DESCRIPTIVE": "Selecciona todos los pixeles no seleccionados y deselecciona los seleccionados",
   "TRANSFORM_SELECTED_AREA": "Transformar zona seleccionada",
   "TRANSFORM_SELECTED_AREA": "Transformar zona seleccionada",
-  "NUDGE_SELECTED_LEFT": "Desplazar el objeto seleccionado hacia la izquierda",
-  "NUDGE_SELECTED_RIGHT": "Desplazar el objeto seleccionado hacia la derecha",
-  "NUDGE_SELECTED_UP": "Desplazar hacia arriba el objeto seleccionado",
-  "NUDGE_SELECTED_DOWN": "Desplazar hacia abajo el objeto seleccionado",
-  "MASK_FROM_SELECTION": "Nueva máscara a partir de la selección",
-  "MASK_FROM_SELECTION_DESCRIPTIVE": "Seleccionar nueva mascara",
-  "ADD_SELECTION_TO_MASK": "Agregar mascara seleccionada",
+  "NUDGE_SELECTED_LEFT": "Mover el objeto seleccionado a la izquierda",
+  "NUDGE_SELECTED_RIGHT": "Mover el objeto seleccionado a la derecha",
+  "NUDGE_SELECTED_UP": "Mover el objeto seleccionado hacia arriba",
+  "NUDGE_SELECTED_DOWN": "Mover el objeto seleccionado hacia abajo",
+  "MASK_FROM_SELECTION": "Nueva máscara por selección",
+  "MASK_FROM_SELECTION_DESCRIPTIVE": "Crea una máscara a partir de la selección activa que reemplaza a la existente",
+  "ADD_SELECTION_TO_MASK": "Agregar selección a máscarn",
   "SUBTRACT_SELECTION_FROM_MASK": "Restar la selección de la máscara",
   "SUBTRACT_SELECTION_FROM_MASK": "Restar la selección de la máscara",
-  "INTERSECT_SELECTION_MASK": "Selección de intersección con máscara",
-  "SELECTION_TO_MASK": "Selección para enmascarar",
-  "TO_NEW_MASK": "a la nueva máscara",
-  "ADD_TO_MASK": "agregar mascara",
-  "SUBTRACT_FROM_MASK": "restar de la máscara",
-  "INTERSECT_WITH_MASK": "intersecar con máscara",
+  "INTERSECT_SELECTION_MASK": "Máscara en base a intersección con selección",
+  "SELECTION_TO_MASK": "Selección a máscara",
+  "TO_NEW_MASK": "Nueva máscara",
+  "ADD_TO_MASK": "Adición con máscara",
+  "SUBTRACT_FROM_MASK": "Substracción de la máscara",
+  "INTERSECT_WITH_MASK": "Intersección con máscara",
   "STYLUS": "Stylus",
   "STYLUS": "Stylus",
-  "TOGGLE_PEN_MODE": "Activar el modo lápiz",
+  "TOGGLE_PEN_MODE": "Activación del modo lápiz",
   "UNDO": "Deshacer",
   "UNDO": "Deshacer",
   "UNDO_DESCRIPTIVE": "Deshacer la última acción",
   "UNDO_DESCRIPTIVE": "Deshacer la última acción",
   "REDO": "Rehacer",
   "REDO": "Rehacer",
   "REDO_DESCRIPTIVE": "Rehacer la última acción",
   "REDO_DESCRIPTIVE": "Rehacer la última acción",
-  "WINDOWS": "Windows",
-  "TOGGLE_GRIDLINES": "Activar cuadrículas",
-  "ZOOM_IN": "Ampliar",
+  "WINDOWS": "Ventanas",
+  "TOGGLE_GRIDLINES": "Activación de grilla de pixeles",
+  "ZOOM_IN": "Acercar",
   "ZOOM_OUT": "Alejar",
   "ZOOM_OUT": "Alejar",
-  "NEW_WINDOW_FOR_IMG": "Nueva ventana para la imagen actual",
+  "NEW_WINDOW_FOR_IMG": "Nueva ventana a partir de imagen actual",
   "CENTER_ACTIVE_VIEWPORT": "Centrar la ventana activa",
   "CENTER_ACTIVE_VIEWPORT": "Centrar la ventana activa",
   "FLIP_VIEWPORT_HORIZONTALLY": "Voltear horizontalmente la ventana gráfica",
   "FLIP_VIEWPORT_HORIZONTALLY": "Voltear horizontalmente la ventana gráfica",
   "FLIP_VIEWPORT_VERTICALLY": "Voltear la ventana verticalmente",
   "FLIP_VIEWPORT_VERTICALLY": "Voltear la ventana verticalmente",
-  "SETTINGS": "Ajustes",
-  "OPEN_SETTINGS": "Abrir ajustes",
+  "SETTINGS": "Configuración",
+  "OPEN_SETTINGS": "Abrir configuración",
   "OPEN_SETTINGS_DESCRIPTIVE": "Abrir la ventana de configuración",
   "OPEN_SETTINGS_DESCRIPTIVE": "Abrir la ventana de configuración",
-  "OPEN_STARTUP_WINDOW": "Abrir la ventana de inicio",
-  "OPEN_SHORTCUT_WINDOW": "Abrir la ventana de accesos directos",
+  "OPEN_STARTUP_WINDOW": "Abrir la ventana de bienvenida",
+  "OPEN_SHORTCUT_WINDOW": "Abrir la ventana de atajos del teclado",
   "OPEN_ABOUT_WINDOW": "Abrir la ventana Acerca de",
   "OPEN_ABOUT_WINDOW": "Abrir la ventana Acerca de",
   "ERROR": "Error",
   "ERROR": "Error",
   "INTERNAL_ERROR": "Error interno",
   "INTERNAL_ERROR": "Error interno",
   "ERROR_SAVE_LOCATION": "No se ha podido guardar el archivo en la ubicación especificada",
   "ERROR_SAVE_LOCATION": "No se ha podido guardar el archivo en la ubicación especificada",
-  "ERROR_WHILE_SAVING": "Se ha producido un error interno al guardar. Por favor, inténtelo de nuevo.",
+  "ERROR_WHILE_SAVING": "Se ha producido un error interno al guardar. Por favor, inténtalo de nuevo.",
   "UNKNOWN_ERROR_SAVING": "Se ha producido un error al guardar.",
   "UNKNOWN_ERROR_SAVING": "Se ha producido un error al guardar.",
   "FAILED_ASSOCIATE_LOSPEC": "Fallo al asociar el protocolo Lospec Palette.",
   "FAILED_ASSOCIATE_LOSPEC": "Fallo al asociar el protocolo Lospec Palette.",
   "REDDIT": "Reddit",
   "REDDIT": "Reddit",
   "GITHUB": "GitHub",
   "GITHUB": "GitHub",
   "YOUTUBE": "YouTube",
   "YOUTUBE": "YouTube",
   "DONATE": "Donar",
   "DONATE": "Donar",
-  "YES": "Si",
+  "YES": "Sí",
   "NO": "No",
   "NO": "No",
   "CANCEL": "Cancelar",
   "CANCEL": "Cancelar",
-  "UNNAMED": "SinNombre",
-  "OPEN_COMMAND_DEBUG_WINDOW": "Abrir ventana de comandos debug",
+  "UNNAMED": "Sin nombre",
+  "OPEN_COMMAND_DEBUG_WINDOW": "Abrir ventana de depuración de comandos",
   "DELETE": "Eliminar",
   "DELETE": "Eliminar",
-  "USER_PREFS": "Preferencias del usuario (Roaming)",
-  "SHORTCUT_FILE": "Archivo de acceso directo (Roaming)",
+  "USER_PREFS": "Preferencias de usuario (Roaming)",
+  "SHORTCUT_FILE": "Archivo de atajos del teclado (Roaming)",
   "EDITOR_DATA": "Editor de datos (Local)",
   "EDITOR_DATA": "Editor de datos (Local)",
-  "MOVE_VIEWPORT_TOOLTIP": "Mueve la ventana gráfica. ({0})",
-  "MOVE_VIEWPORT_ACTION_DISPLAY": "Haz clic y desplázate para desplazar la ventana gráfica",
-  "MOVE_TOOL_TOOLTIP": "Mueve los píxeles seleccionados ({0}). Mantenga pulsada la tecla Ctrl para mover todas las capas.",
+  "MOVE_VIEWPORT_TOOLTIP": "Mueve el área de visualización. ({0})",
+  "MOVE_VIEWPORT_ACTION_DISPLAY": "Haz clic y arrastra para desplazar el área de visualización",
+  "MOVE_TOOL_TOOLTIP": "Selecciona y transforma capas ({0}).",
   "MOVE_TOOL_ACTION_DISPLAY": "Mantenga pulsado el ratón para mover los píxeles seleccionados. Mantenga pulsada la tecla Ctrl para mover todas las capas.",
   "MOVE_TOOL_ACTION_DISPLAY": "Mantenga pulsado el ratón para mover los píxeles seleccionados. Mantenga pulsada la tecla Ctrl para mover todas las capas.",
-  "PEN_TOOL_TOOLTIP": "Lapiz. ({0})",
+  "PEN_TOOL_TOOLTIP": "Lápiz. ({0})",
   "PEN_TOOL_ACTION_DISPLAY": "Haz clic y muévete para dibujar.",
   "PEN_TOOL_ACTION_DISPLAY": "Haz clic y muévete para dibujar.",
-  "PIXEL_PERFECT_SETTING": "Pixel perfecto",
-  "RECTANGLE_TOOL_TOOLTIP": "Dibuja un rectángulo en el canvas ({0}). Mantenga pulsada la tecla Mayús para dibujar un cuadrado.",
-  "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Haga clic y mueva para dibujar un rectángulo. Mantenga pulsada la tecla Mayús para dibujar un cuadrado.",
-  "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Haz clic y mueve para dibujar un cuadrado.",
-  "KEEP_ORIGINAL_IMAGE_SETTING": "Mantener archivo original",
-  "ROTATE_VIEWPORT_TOOLTIP": "Rotar la vista. ({0})",
-  "ROTATE_VIEWPORT_ACTION_DISPLAY": "Haga clic y mueva para girar la ventana gráfica",
-  "SELECT_TOOL_TOOLTIP": "Seleccionar area. ({0})",
-  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Haga clic y mueva para seleccionar un área. Mantenga pulsada la tecla Mayús para añadir a la selección existente. Mantenga pulsada la tecla Ctrl para restar.",
-  "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Pulse y mueva para añadir a la selección actual.",
-  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Haga clic y mueva para restar de la selección actual.",
+  "PIXEL_PERFECT_SETTING": "Sin Ls",
+  "RECTANGLE_TOOL_TOOLTIP": "Dibuja un rectángulo en el lienzo ({0}). Mantén pulsado Mayús para dibujar un cuadrado.",
+  "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Haz clic y arrastra para dibujar un rectángulo. Mantén pulsado Mayús para dibujar un cuadrado.",
+  "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Haz clic y arrastra para dibujar un cuadrado.",
+  "KEEP_ORIGINAL_IMAGE_SETTING": "Copiar área antes de mover",
+  "ROTATE_VIEWPORT_TOOLTIP": "Rota el área de visualización. ({0})",
+  "ROTATE_VIEWPORT_ACTION_DISPLAY": "Haz clic y arrastra para rotar el área de visualización",
+  "SELECT_TOOL_TOOLTIP": "Selecciona un área. ({0})",
+  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Haz clic y arrastra para seleccionar un área. Mantén pulsado Mayús para adición. Mantén pulsado Control para substracción.",
+  "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Haz clic y arrastra para selección con adición.",
+  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Haz clic y arrastra para selección con substracción.",
   "ZOOM_TOOL_TOOLTIP": "Amplía la vista ({0}). Haz clic para acercar, mantén pulsado alt y haz clic para alejar.",
   "ZOOM_TOOL_TOOLTIP": "Amplía la vista ({0}). Haz clic para acercar, mantén pulsado alt y haz clic para alejar.",
-  "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Haz clic y muévete para ampliar. Haz clic para acercar, mantén pulsada la tecla ctrl y haz clic para alejar.",
-  "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Haz clic y muévete para ampliar. Haz clic para alejar, suelta ctrl y haz clic para acercar.",
-  "BRIGHTNESS_TOOL_TOOLTIP": "Aclara u oscurece los píxeles ({0}). Mantenga pulsada la tecla Ctrl para oscurecer los píxeles.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Dibuja sobre los píxeles para hacerlos más brillantes. Mantenga pulsada la tecla Ctrl para oscurecer.",
-  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Dibuja sobre los píxeles para oscurecerlos. Suelte Ctrl para aclarar.",
-  "COLOR_PICKER_TOOLTIP": "Elige el color primario del canvas. ({0})",
-  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Haz clic para elegir colores. Mantenga pulsada la tecla Ctrl para ocultar el canvas. Mantenga pulsada la tecla Shift para ocultar la referencia.",
-  "ELLIPSE_TOOL_TOOLTIP": "Dibuja una elipse en el canvas ({0}). Mantenga pulsada la tecla Shift para dibujar un círculo.",
+  "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Haz clic y arrastra para cambiar el aumento. Clic para acercarse, control + clic para alejarse.",
+  "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Haz clic y arrastra para cambiar el aumento. Clic para alejarse, deja de presionar control y haz clic para acercarse.",
+  "BRIGHTNESS_TOOL_TOOLTIP": "Aclara u oscurece los píxeles ({0}). Mantén pulsado control para oscurecer los pixeles.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Dibuja sobre los pixeles para hacerlos más brillantes. Mantén pulsado control para oscurecerlos.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Dibuja sobre los pixeles para oscurecerlos. Suelta Control para aclararlos.",
+  "COLOR_PICKER_TOOLTIP": "Elige el color primario del lienzo. ({0})",
+  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Haz clic para elegir colores. Mantén pulsado control para ocultar el lienzo. Mantén pulsado Mayús para ocultar la capa de referencia",
+  "ELLIPSE_TOOL_TOOLTIP": "Dibuja una elipse en el lienzo ({0}). Mantén pulsado mayús para dibujar un círculo.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Haz clic y mueve el ratón para dibujar una elipse. Mantenga pulsada la tecla Shift para dibujar un círculo.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Haz clic y mueve el ratón para dibujar una elipse. Mantenga pulsada la tecla Shift para dibujar un círculo.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Haz clic y mueve el ratón para dibujar un círculo.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Haz clic y mueve el ratón para dibujar un círculo.",
   "ERASER_TOOL_TOOLTIP": "Color del borrador del píxel. ({0})",
   "ERASER_TOOL_TOOLTIP": "Color del borrador del píxel. ({0})",
@@ -444,7 +437,6 @@
   "DELETE_PALETTE_CONFIRMATION": "¿Está seguro de que desea eliminar esta paleta? Esto no se puede deshacer.",
   "DELETE_PALETTE_CONFIRMATION": "¿Está seguro de que desea eliminar esta paleta? Esto no se puede deshacer.",
   "SHORTCUTS_IMPORTED": "Los accesos directos de {0} se han importado correctamente.",
   "SHORTCUTS_IMPORTED": "Los accesos directos de {0} se han importado correctamente.",
   "SHORTCUT_PROVIDER_DETECTED": "Hemos detectado que tiene {0} instalado. ¿Desea importar accesos directos desde él?",
   "SHORTCUT_PROVIDER_DETECTED": "Hemos detectado que tiene {0} instalado. ¿Desea importar accesos directos desde él?",
-  "IMPORT_FROM_INSTALLATION": "Importar desde la instalación",
   "IMPORT_INSTALLATION_OPTION1": "Importar desde la instalación",
   "IMPORT_INSTALLATION_OPTION1": "Importar desde la instalación",
   "IMPORT_INSTALLATION_OPTION2": "Utilizar valores por defecto",
   "IMPORT_INSTALLATION_OPTION2": "Utilizar valores por defecto",
   "IMPORT_FROM_TEMPLATE": "Importar desde plantilla",
   "IMPORT_FROM_TEMPLATE": "Importar desde plantilla",
@@ -467,7 +459,6 @@
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Este acceso directo ya está asignado a '{0}'.\n¿Desea sustituir el acceso directo existente?",
   "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Este acceso directo ya está asignado a '{0}'.\n¿Desea sustituir el acceso directo existente?",
   "UNSAVED_CHANGES": "Cambios no guardados",
   "UNSAVED_CHANGES": "Cambios no guardados",
   "DOCUMENT_MODIFIED_SAVE": "El documento ha sido modificado. ¿Desea guardar los cambios?",
   "DOCUMENT_MODIFIED_SAVE": "El documento ha sido modificado. ¿Desea guardar los cambios?",
-  "SESSION_UNSAVED_DATA": "{0} con datos sin guardar. ¿Está seguro?",
   "PROJECT_MAINTAINERS": "Mantenedores de proyectos",
   "PROJECT_MAINTAINERS": "Mantenedores de proyectos",
   "OTHER_AWESOME_CONTRIBUTORS": "Y otros increíbles colaboradores",
   "OTHER_AWESOME_CONTRIBUTORS": "Y otros increíbles colaboradores",
   "HELP": "Ayuda",
   "HELP": "Ayuda",
@@ -479,10 +470,9 @@
   "REFERENCE": "Referencia",
   "REFERENCE": "Referencia",
   "PUT_REFERENCE_LAYER_ABOVE": "Poner capa de referencia encima",
   "PUT_REFERENCE_LAYER_ABOVE": "Poner capa de referencia encima",
   "PUT_REFERENCE_LAYER_BELOW": "Poner capa de referencia debajo",
   "PUT_REFERENCE_LAYER_BELOW": "Poner capa de referencia debajo",
-  "TOGGLE_VERTICAL_SYMMETRY": "Alternar simetría vertical",
-  "TOGGLE_HORIZONTAL_SYMMETRY": "Alternar simetría horizontal",
+  "TOGGLE_VERTICAL_SYMMETRY": "Activación de simetría vertical",
+  "TOGGLE_HORIZONTAL_SYMMETRY": "Activación de simetría horizontal",
   "RESET_VIEWPORT": "Restablecer vista",
   "RESET_VIEWPORT": "Restablecer vista",
-  "VIEWPORT_SETTINGS": "Viewport settings",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Mantenga pulsado el ratón para mover los píxeles de las capas seleccionadas.",
   "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Mantenga pulsado el ratón para mover los píxeles de las capas seleccionadas.",
   "CTRL_KEY": "Ctrl",
   "CTRL_KEY": "Ctrl",
   "SHIFT_KEY": "Shift",
   "SHIFT_KEY": "Shift",
@@ -507,7 +497,6 @@
   "SECURITY_ERROR_MSG": "No hay derechos para escribir en la ubicación especificada.",
   "SECURITY_ERROR_MSG": "No hay derechos para escribir en la ubicación especificada.",
   "IO_ERROR": "IO error",
   "IO_ERROR": "IO error",
   "IO_ERROR_MSG": "Error al escribir en disco.",
   "IO_ERROR_MSG": "Error al escribir en disco.",
-  "FAILED_ASSOCIATE_PIXI": "Error al asociar el archivo .pixi con PixiEditor.",
   "COULD_NOT_SAVE_PALETTE": "Se ha producido un error al guardar la paleta.",
   "COULD_NOT_SAVE_PALETTE": "Se ha producido un error al guardar la paleta.",
   "NO_COLORS_TO_SAVE": "No hay colores que guardar.",
   "NO_COLORS_TO_SAVE": "No hay colores que guardar.",
   "SINGLE_LAYER": "Unica capa",
   "SINGLE_LAYER": "Unica capa",
@@ -543,5 +532,11 @@
   "SOURCE_UNSET_OR_MISSING": "Fuente ausente/desactivada",
   "SOURCE_UNSET_OR_MISSING": "Fuente ausente/desactivada",
   "SOURCE_NEWER": "Fuente más reciente",
   "SOURCE_NEWER": "Fuente más reciente",
   "SOURCE_UP_TO_DATE": "La fuente está actualizada",
   "SOURCE_UP_TO_DATE": "La fuente está actualizada",
-  "SOURCE_OLDER": "Nube más reciente"
+  "SOURCE_OLDER": "Nube más reciente",
+  "NEWS": "Noticias",
+  "DISABLE_NEWS_PANEL": "No mostrar el panel de noticias en la ventana de bienvenida",
+  "FAILED_FETCH_NEWS": "No se pudieron cargar las noticias",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Pérdida de documentos tras colapso",
+  "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "No todos los documentos pudieron ser recuperados tras un cierre inesperado. Metele ganas a guardar tu trabajo.",
+  "EXAMPLE_FILES": "Archivos de ejemplo"
 }
 }

+ 691 - 0
src/PixiEditor/Data/Localization/Languages/fr.json

@@ -0,0 +1,691 @@
+{
+  "RECENT_FILES": "Fichiers récents",
+  "OPEN_FILE": "Ouvrir un fichier",
+  "NEW_FILE": "Nouveau",
+  "RECENT_EMPTY_TEXT": "Sacrément vide...",
+  "LANGUAGE": "Langue",
+  "GENERAL": "Général",
+  "DISCORD": "Discord",
+  "KEY_BINDINGS": "Raccourcis clavier",
+  "MISC": "Divers",
+  "SHOW_STARTUP_WINDOW": "Afficher la fenêtre de démarrage",
+  "RECENT_FILE_LENGTH": "Longueur de la liste des fichiers récents",
+  "RECENT_FILE_LENGTH_TOOLTIP": "Combien de documents sont affichés  sous Fichier > Récents. Par défaut : 8",
+  "DEFAULT_NEW_SIZE": "Taille par défaut des nouveaux fichiers",
+  "WIDTH": "Largeur",
+  "HEIGHT": "Hauteur",
+  "TOOLS": "Outils",
+  "ENABLE_SHARED_TOOLBAR": "Activer la barre d'outils partagée",
+  "AUTOMATIC_UPDATES": "Mises à jour automatiques",
+  "CHECK_FOR_UPDATES": "Vérifier les mises à jour au démarrage",
+  "UPDATE_STREAM": "Flux de mise à jour",
+  "UPDATE_CHANNEL_HELP_TOOLTIP": "Les flux de mise à jour ne peuvent qu'être changées dans la version indépendante (téléchargée via https://pixieditor.net).\nLes versions Steam et Microsoft Store gèrent séparément les mise à jour.",
+  "DEBUG": "Débogage",
+  "ENABLE_DEBUG_MODE": "Activer le mode de débogage",
+  "OPEN_CRASH_REPORTS_DIR": "Ouvrir le dossier des rapports de plantage",
+  "DISCORD_RICH_PRESENCE": "Présence d'activité",
+  "ENABLED": "Activer",
+  "SHOW_IMAGE_NAME": "Afficher le nom de l'image",
+  "SHOW_IMAGE_SIZE": "Afficher la taille de l'image",
+  "SHOW_LAYER_COUNT": "Afficher le nombre de couches",
+  "FILE": "Fichier",
+  "RECENT": "Récent",
+  "OPEN": "Ouvrir",
+  "SAVE_PIXI": "Sauvegarder (.pixi)",
+  "SAVE_AS_PIXI": "Sauvegarder en tant que... (.pixi)",
+  "EXPORT_IMG": "Exporter (.png, .jpg etc...)",
+  "EDIT": "Éditer",
+  "EXIT": "Quitter",
+  "PERCENTAGE": "Pourcentage",
+  "ABSOLUTE": "Absolue",
+  "PRESERVE_ASPECT_RATIO": "Conserver les proportions",
+  "ANCHOR_POINT": "Point d'ancrage",
+  "RESIZE_IMAGE": "Redimensionner l'image",
+  "RESIZE": "Redimensionner",
+  "DOCUMENTATION": "Documentation",
+  "WEBSITE": "Site internet",
+  "OPEN_WEBSITE": "Ouvrir le site internet",
+  "REPOSITORY": "Dépôt Git",
+  "OPEN_REPOSITORY": "Ouvrir le dépôt",
+  "LICENSE": "Licence",
+  "OPEN_LICENSE": "Ouvrir la licence",
+  "THIRD_PARTY_LICENSES": "Licences tierces",
+  "OPEN_THIRD_PARTY_LICENSES": "Ouvrir les licences tierces",
+  "APPLY_TRANSFORM": "Appliquer la transformation",
+  "INCREASE_TOOL_SIZE": "Augmenter la taille de l'outil",
+  "DECREASE_TOOL_SIZE": "Diminuer la taille de l'outil",
+  "DOWNLOADING_UPDATE": "Téléchargement de la mise à jour...",
+  "UPDATE_READY": "La mise à jour est prête à être installée. Voulez-vous l'installer maintenant ?",
+  "COULD_NOT_UPDATE_WITHOUT_ADMIN": "Impossible de mettre à jour sans les droits Administrateur. Veuillez exécuter PixiEditor en tant qu'administrateur.",
+  "INSUFFICIENT_PERMISSIONS": "Permissions insuffisantes",
+  "VERSION": "Version {0}",
+  "OPEN_TEMP_DIR": "Ouvrir le dossier temp",
+  "OPEN_LOCAL_APPDATA_DIR": "Ouvrir le dossier Local AppData",
+  "OPEN_ROAMING_APPDATA_DIR": "Ouvrir le dossier Roaming AppData",
+  "OPEN_INSTALLATION_DIR": "Ouvrir le dossier d'installation",
+  "DUMP_ALL_COMMANDS": "Vider toutes les commandes",
+  "DUMP_ALL_COMMANDS_DESCRIPTIVE": "Vide toutes les commandes dans un fichier texte",
+  "CRASH": "Plantage",
+  "CRASH_APP": "Faire planter l'application",
+  "DELETE_USR_PREFS": "Supprimer les préférences utilisateurs (Roaming AppData)",
+  "DELETE_SHORTCUT_FILE": "Supprimer le fichier de raccourcis (Roaming AppData)",
+  "DELETE_EDITOR_DATA": "Supprimer les données de l'éditeur (Local AppData)",
+  "GENERATE_KEY_BINDINGS_TEMPLATE": "Générer un modèle de raccourcis clavier",
+  "GENERATE_KEY_BINDINGS_TEMPLATE_DESCRIPTIVE": "Génère un modèle json des raccourcis clavier",
+  "VALIDATE_SHORTCUT_MAP": "Valider la liste des raccourcis",
+  "VALIDATE_SHORTCUT_MAP_DESCRIPTIVE": "Validation de la liste des raccourcis",
+  "VALIDATION_KEYS_NOTICE_DIALOG": "Touches vides : {0}\nCommandes inconnues : {1}",
+  "RESULT": "Résultat",
+  "CLEAR_RECENT_DOCUMENTS": "Effacer la liste des documents récents",
+  "CLEAR_RECENTLY_OPENED_DOCUMENTS": "Effacer la liste des documents récemment ouverts",
+  "OPEN_CMD_DEBUG_WINDOW": "Ouvrir la fenêtre des commandes de débogage",
+  "PATH_DOES_NOT_EXIST": "{0} n'existe pas.",
+  "LOCATION_DOES_NOT_EXIST": "Ce chemin d'accès n'existe pas.",
+  "FILE_NOT_FOUND": "Fichier non trouvé.",
+  "ARE_YOU_SURE": "Êtes-vous sûr ?",
+  "ARE_YOU_SURE_PATH_FULL_PATH": "Souhaitez-vous réellement supprimer {0} ?\nCette donnée sera effacée pour toutes les installations.\n(Chemin complet : {1})",
+  "FAILED_TO_OPEN_FILE": "Échec de l'ouverture du fichier",
+  "OLD_FILE_FORMAT": "Ancien format de fichier",
+  "OLD_FILE_FORMAT_DESCRIPTION": "Ce fichier .pixi utilise l'ancien format,\nqui n'est plus supporté et ne peut pas être ouvert.",
+  "NOTHING_FOUND": "Rien n'a été trouvé",
+  "EXPORT": "Exporter",
+  "EXPORT_IMAGE": "Exporter l'image",
+  "IMPORT": "Importer",
+  "SHORTCUT_TEMPLATES": "Modèle de raccourcis",
+  "RESET_ALL": "Tout réinitialiser",
+  "LAYER": "Couche",
+  "LAYER_DELETE_ALL_SELECTED": "Supprimer toutes les couches/tous les dossiers sélectionnés",
+  "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE": "Supprime toutes les couches et/ou les dossiers sélectionnés",
+  "NEW_FOLDER": "Nouveau dossier",
+  "CREATE_NEW_FOLDER": "Créer un nouveau dossier",
+  "NEW_LAYER": "Nouvelle couche",
+  "CREATE_NEW_LAYER": "Créer une nouvelle couche",
+  "NEW_IMAGE": "Nouvelle image",
+  "CREATE_NEW_IMAGE": "Créer une nouvelle image",
+  "SAVE": "Sauvegarder",
+  "SAVE_AS": "Sauvegarder en tant que...",
+  "IMAGE": "Image",
+  "SAVE_IMAGE": "Sauvegarder l'image",
+  "SAVE_IMAGE_AS": "Sauvegarder l'image en tant que nouvelle",
+  "DUPLICATE": "Dupliquer",
+  "DUPLICATE_SELECTED_LAYER": "Dupliquer la couche sélectionnée",
+  "CREATE_MASK": "Créer un masque",
+  "DELETE_MASK": "Supprimer le masque",
+  "TOGGLE_MASK": "Activer/cacher le masque",
+  "APPLY_MASK": "Appliquer le masque",
+  "TOGGLE_VISIBILITY": "Activer/désactiver la visibilité",
+  "MOVE_MEMBER_UP": "Déplacer l'élément vers le haut",
+  "MOVE_MEMBER_UP_DESCRIPTIVE": "Déplace la couche ou le dossier sélectionné vers le haut",
+  "MOVE_MEMBER_DOWN": "Déplacer l'élément vers le bas",
+  "MOVE_MEMBER_DOWN_DESCRIPTIVE": "Déplace la couche ou le dossier sélectionné vers le bas",
+  "MERGE_ALL_SELECTED_LAYERS": "Fusionner toutes les couches sélectionnées",
+  "MERGE_WITH_ABOVE": "Fusionner la couche sélectionnée avec celle du dessus",
+  "MERGE_WITH_ABOVE_DESCRIPTIVE": "Fusionne la couche sélectionnée avec celle juste au-dessus",
+  "MERGE_WITH_BELOW": "Fusionner la couche sélectionnée avec celle du dessous",
+  "MERGE_WITH_BELOW_DESCRIPTIVE": "Fusionne la couche sélectionnée avec celle juste en-dessous",
+  "ADD_REFERENCE_LAYER": "Ajouter une couche de référence",
+  "DELETE_REFERENCE_LAYER": "Supprimer la couche de référence",
+  "TRANSFORM_REFERENCE_LAYER": "Transformer la couche de référence",
+  "TOGGLE_REFERENCE_LAYER_POS": "Activer la position de la couche de référence",
+  "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE": "Active la couche de référence entre la toute première ou la toute dernière",
+  "RESET_REFERENCE_LAYER_POS": "Réinitialiser la position de la couche de référence",
+  "CLIP_CANVAS": "Découper le canevas",
+  "FLIP_IMG_VERTICALLY": "Retourner verticalement l'image",
+  "FLIP_IMG_HORIZONTALLY": "Retourner horizontalement l'image",
+  "FLIP_LAYERS_VERTICALLY": "Retourner verticalement les couches sélectionnées",
+  "FLIP_LAYERS_HORIZONTALLY": "Retourner horizontalement les couches sélectionnées",
+  "ROT_IMG_90": "Pivoter l'image de 90 degrés",
+  "ROT_IMG_180": "Pivoter l'image de 180 degrés",
+  "ROT_IMG_-90": "Pivoter l'image de -90 degrés",
+  "ROT_LAYERS_90": "Pivoter les couches sélectionnées de 90 degrés",
+  "ROT_LAYERS_180": "Pivoter les couches sélectionnées de 180 degrés",
+  "ROT_LAYERS_-90": "Pivoter les couches sélectionnées de -90 degrés",
+  "TOGGLE_VERT_SYMMETRY_AXIS": "Activer l'axe de symétrie verticale",
+  "TOGGLE_HOR_SYMMETRY_AXIS": "Activer l'axe de symétrie horizontale",
+  "RESIZE_DOCUMENT": "Redimensionner le document",
+  "RESIZE_CANVAS": "Redimensionner le canevas",
+  "CENTER_CONTENT": "Centrer le contenu",
+  "CUT": "Couper",
+  "CUT_DESCRIPTIVE": "Coupe la zone/couches sélectionnées",
+  "PASTE": "Coller",
+  "PASTE_DESCRIPTIVE": "Colle le contenu du presse-papiers",
+  "PASTE_AS_NEW_LAYER": "Coller en tant que nouvelle couche",
+  "PASTE_AS_NEW_LAYER_DESCRIPTIVE": "Colle depuis le presse-papiers en tant que nouvelle couche",
+  "PASTE_REFERENCE_LAYER": "Coller la couche de référence",
+  "PASTE_REFERENCE_LAYER_DESCRIPTIVE": "Colle le contenu du presse-papiers en tant que couche de référence",
+  "PASTE_COLOR": "Coller la couleur",
+  "PASTE_COLOR_DESCRIPTIVE": "Colle la couleur depuis le presse-papiers",
+  "PASTE_COLOR_SECONDARY": "Coller en tant que couleur secondaire",
+  "PASTE_COLOR_SECONDARY_DESCRIPTIVE": "Colle la couleur depuis le presse-papiers en tant que couleur secondaire",
+  "CLIPBOARD": "Presse-papiers",
+  "COPY": "Copier",
+  "COPY_DESCRIPTIVE": "Copie vers le presse-papiers",
+  "COPY_COLOR_HEX": "Copier la couleur primaire (HEX)",
+  "COPY_COLOR_HEX_DESCRIPTIVE": "Copie le code hexadécimal de la couleur primaire",
+  "COPY_COLOR_RGB": "Copier la couleur primaire (RGB)",
+  "COPY_COLOR_RGB_DESCRIPTIVE": "Copie le code rouge-vert-bleu de la couleur primaire",
+  "COPY_COLOR_SECONDARY_HEX": "Copier la couleur secondaire (HEX)",
+  "COPY_COLOR_SECONDARY_HEX_DESCRIPTIVE": "Copie le code hexadécimal de la couleur secondaire",
+  "COPY_COLOR_SECONDARY_RGB": "Copier la couleur secondaire (RGB)",
+  "COPY_COLOR_SECONDARY_RGB_DESCRIPTIVE": "Copie le code rouge-vert-bleu de la couleur secondaire",
+  "PALETTE_COLORS": "Palette de couleurs",
+  "REPLACE_SECONDARY_BY_PRIMARY": "Remplacer la couleur secondaire par la primaire",
+  "REPLACE_PRIMARY_BY_SECONDARY": "Remplacer la couleur primaire par la secondaire",
+  "REPLACE_PRIMARY_BY_SECONDARY_DESCRIPTIVE": "Remplace la couleur primaire par la couleur secondaire",
+  "OPEN_PALETTE_BROWSER": "Ouvrir le navigateur de palettes",
+  "OVERWRITE_PALETTE_CONSENT": "La palette '{0}' existe déjà, voulez-vous la remplacer ?",
+  "PALETTE_EXISTS": "La palette existe déjà",
+  "REPLACE_PALETTE_CONSENT": "Remplacer la palette actuelle avec celle sélectionnée ?",
+  "REPLACE_PALETTE": "Remplacer la palette actuelle",
+  "SELECT_COLOR_1": "Sélectionner la couleur 1",
+  "SELECT_COLOR_2": "Sélectionner la couleur 2",
+  "SELECT_COLOR_3": "Sélectionner la couleur 3",
+  "SELECT_COLOR_4": "Sélectionner la couleur 4",
+  "SELECT_COLOR_5": "Sélectionner la couleur 5",
+  "SELECT_COLOR_6": "Sélectionner la couleur 6",
+  "SELECT_COLOR_7": "Sélectionner la couleur 7",
+  "SELECT_COLOR_8": "Sélectionner la couleur 8",
+  "SELECT_COLOR_9": "Sélectionner la couleur 9",
+  "SELECT_COLOR_10": "Sélectionner la couleur 10",
+  "SELECT_TOOL": "Sélectionner l'outil {0}",
+  "SELECT_COLOR_1_DESCRIPTIVE": "Sélectionne la première couleur de la palette",
+  "SELECT_COLOR_2_DESCRIPTIVE": "Sélectionne la seconde couleur de la palette",
+  "SELECT_COLOR_3_DESCRIPTIVE": "Sélectionne la troisième couleur de la palette",
+  "SELECT_COLOR_4_DESCRIPTIVE": "Sélectionne la quatrième couleur de la palette",
+  "SELECT_COLOR_5_DESCRIPTIVE": "Sélectionne la cinquième couleur de la palette",
+  "SELECT_COLOR_6_DESCRIPTIVE": "Sélectionne la sixième couleur de la palette",
+  "SELECT_COLOR_7_DESCRIPTIVE": "Sélectionne la septième couleur de la palette",
+  "SELECT_COLOR_8_DESCRIPTIVE": "Sélectionne la huitième couleur de la palette",
+  "SELECT_COLOR_9_DESCRIPTIVE": "Sélectionne la neuvième couleur de la palette",
+  "SELECT_COLOR_10_DESCRIPTIVE": "Sélectionne la dixième couleur de la palette",
+  "SWAP_COLORS": "Échanger les couleurs",
+  "SWAP_COLORS_DESCRIPTIVE": "Échanger la couleur primaire et la couleur secondaire",
+  "SEARCH": "Rechercher",
+  "COMMAND_SEARCH": "Rechercher la commande",
+  "OPEN_COMMAND_SEARCH": "Ouvrir la fenêtre de recherche de commandes",
+  "SELECT": "Sélectionner",
+  "DESELECT": "Déselectionner",
+  "INVERT": "Inverser",
+  "SELECTION": "Sélection",
+  "SELECT_ALL": "Tout sélectionner",
+  "SELECT_ALL_DESCRIPTIVE": "Sélectionne l'intégralité du canevas",
+  "CLEAR_SELECTION": "Effacer la sélection",
+  "INVERT_SELECTION": "Inverser la sélection",
+  "INVERT_SELECTION_DESCRIPTIVE": "Inverse la zone sélectionnée",
+  "TRANSFORM_SELECTED_AREA": "Transformer la zone sélectionnée",
+  "NUDGE_SELECTED_LEFT": "Déplacer l'objet sélectionné vers la gauche",
+  "NUDGE_SELECTED_RIGHT": "Déplacer l'objet sélectionné vers la droite",
+  "NUDGE_SELECTED_UP": "Déplacer l'objet sélectionné vers le haut",
+  "NUDGE_SELECTED_DOWN": "Déplacer l'objet sélectionné vers le bas",
+  "MASK_FROM_SELECTION": "Nouveau masque depuis la sélection",
+  "MASK_FROM_SELECTION_DESCRIPTIVE": "Déplace la sélection sur un nouveau masque",
+  "ADD_SELECTION_TO_MASK": "Ajouter la sélection au masque",
+  "SUBTRACT_SELECTION_FROM_MASK": "Retirer la sélection du masque",
+  "INTERSECT_SELECTION_MASK": "Intersection de la sélection avec le masque",
+  "SELECTION_TO_MASK": "Sélection vers masque",
+  "TO_NEW_MASK": "Vers un nouveau masque",
+  "ADD_TO_MASK": "Ajouter au masque",
+  "SUBTRACT_FROM_MASK": "Retirer du masque",
+  "INTERSECT_WITH_MASK": "Intersection avec le masque",
+  "STYLUS": "Stylet",
+  "TOGGLE_PEN_MODE": "Activer le mode stylo",
+  "UNDO": "Annuler",
+  "UNDO_DESCRIPTIVE": "Annule la dernière action",
+  "REDO": "Refaire",
+  "REDO_DESCRIPTIVE": "Refait la dernière action",
+  "WINDOWS": "Fenêtres",
+  "TOGGLE_GRIDLINES": "Activer la grille",
+  "ZOOM_IN": "Zoomer",
+  "ZOOM_OUT": "Dézoomer",
+  "NEW_WINDOW_FOR_IMG": "Nouvelle fenêtre pour l'image actuelle",
+  "CENTER_ACTIVE_VIEWPORT": "Centrer l'affichage actif",
+  "FLIP_VIEWPORT_HORIZONTALLY": "Inverser verticalement l'affichage",
+  "FLIP_VIEWPORT_VERTICALLY": "Inverser horizontalement l'affichage",
+  "SETTINGS": "Paramètres",
+  "OPEN_SETTINGS": "Ouvrir les paramètres",
+  "OPEN_SETTINGS_DESCRIPTIVE": "Ouvre la fenêtre des paramètres",
+  "OPEN_STARTUP_WINDOW": "Ouvrir la fenêtre de démarrage",
+  "OPEN_SHORTCUT_WINDOW": "Ouvrir la fenêtre des raccourcis",
+  "OPEN_ABOUT_WINDOW": "Ouvrir la fenêtre À Propos",
+  "ERROR": "Erreur",
+  "INTERNAL_ERROR": "Erreur interne",
+  "ERROR_SAVE_LOCATION": "Échec de la sauvegarde du fichier à l'emplacement spécifié",
+  "ERROR_WHILE_SAVING": "Une erreur interne est survenue lors de la sauvegarde. Veuillez réessayer.",
+  "UNKNOWN_ERROR_SAVING": "Une erreur est survenue lors de la sauvegarde.",
+  "FAILED_ASSOCIATE_LOSPEC": "Échec de l'association avec le protocole de palette de Lospec.",
+  "REDDIT": "Reddit",
+  "GITHUB": "GitHub",
+  "YOUTUBE": "YouTube",
+  "DONATE": "Faire un don",
+  "YES": "Oui",
+  "NO": "Non",
+  "CANCEL": "Annuler",
+  "UNNAMED": "Sans nom",
+  "OPEN_COMMAND_DEBUG_WINDOW": "Ouvrir la fenêtre des commandes de débogage",
+  "DELETE": "Supprimer",
+  "USER_PREFS": "Préférences utilisateur (Roaming)",
+  "SHORTCUT_FILE": "Fichier des raccourcis (Roaming)",
+  "EDITOR_DATA": "Données de l'éditeur (Local)",
+  "MOVE_VIEWPORT_TOOLTIP": "Déplacer l'affichage. ({0})",
+  "MOVE_VIEWPORT_ACTION_DISPLAY": "Cliquez et bouger pour déplacer l'affichage",
+  "MOVE_TOOL_TOOLTIP": "Déplacer les pixels sélectionnés ({0}). Maintenez Ctrl pour déplacer toutes les couches.",
+  "MOVE_TOOL_ACTION_DISPLAY": "Maintenez la souris pour déplacer les pixels sélectionnés. Maintenez Ctrl pour déplacer toutes les couches.",
+  "PEN_TOOL_TOOLTIP": "Stylo. ({0})",
+  "PEN_TOOL_ACTION_DISPLAY": "Cliquez et déplacez pour dessiner.",
+  "PIXEL_PERFECT_SETTING": "Pixel perfect",
+  "RECTANGLE_TOOL_TOOLTIP": "Dessine un rectangle sur le canevas ({0}). Maintenez Shift pour dessiner un carré.",
+  "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT": "Cliquez et déplacez pour dessiner un rectangle. Maintenez Shift pour dessiner un carré.",
+  "RECTANGLE_TOOL_ACTION_DISPLAY_SHIFT": "Cliquez et déplacer pour dessiner un carré.",
+  "KEEP_ORIGINAL_IMAGE_SETTING": "Garder l'image originale",
+  "ROTATE_VIEWPORT_TOOLTIP": "Faire pivoter l'affichage. ({0})",
+  "ROTATE_VIEWPORT_ACTION_DISPLAY": "Cliquez et bouger pour faire pivoter l'affichage",
+  "SELECT_TOOL_TOOLTIP": "Sélectionner la zone. ({0})",
+  "SELECT_TOOL_ACTION_DISPLAY_DEFAULT": "Cliquez et déplacer pour sélectionner une zone. Maintenez Shift pour ajouter à une sélection existante. Maintenez Ctrl pour la soustraire de celle-ci.",
+  "SELECT_TOOL_ACTION_DISPLAY_SHIFT": "Cliquez et déplacer pour ajouter à la sélection actuelle.",
+  "SELECT_TOOL_ACTION_DISPLAY_CTRL": "Cliquez et déplacez pour soustraire de la sélection actuelle.",
+  "ZOOM_TOOL_TOOLTIP": "Zoom sur l'affichage ({0}). Cliquez pour zoomer, maintenez alt et cliquez pour dézoomer.",
+  "ZOOM_TOOL_ACTION_DISPLAY_DEFAULT": "Cliquez et déplacez pour zoomer. Cliquez pour zoomer, maintenez ctrl et cliquez pour dézoomer.",
+  "ZOOM_TOOL_ACTION_DISPLAY_CTRL": "Cliquez et déplacez pour zoomer. Cliquez pour dézoomer, relâchez ctrl et cliquez pour zoomer.",
+  "BRIGHTNESS_TOOL_TOOLTIP": "Rends les pixels plus clairs ou plus foncés ({0})). Maintenez Ctrl pour rendre les pixels plus foncés.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT": "Dessiner sur les pixels pour les rendre plus clair. Maintenez Ctrl pour rendre plus foncés.",
+  "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Dessiner sur les pixels pour les rendre plus foncés. Lâchez Ctrl pour rendre plus clair.",
+  "COLOR_PICKER_TOOLTIP": "Sélectionnez la couleur primaire depuis le canevas. ({0})",
+  "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Cliquez pour sélectionner des couleurs. Maintenez Ctrl pour cacher le canevas. Maintenez Shift pour cacher la couche de référence",
+  "ELLIPSE_TOOL_TOOLTIP": "Dessine une ellipse sur le canevas ({0}). Maintenir Shift pour dessiner un cercle.",
+  "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Cliquez et déplacez la souris pour dessiner une ellipse. Maintenez Shift pour dessiner un cercle.",
+  "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Cliquez et déplacez la souris pour dessiner un cercle.",
+  "ERASER_TOOL_TOOLTIP": "Effacer la couleur du pixel. ({0})",
+  "ERASER_TOOL_ACTION_DISPLAY": "Cliquez et déplacer pour effacer.",
+  "FLOOD_FILL_TOOL_TOOLTIP": "Remplir la zone avec la couleur. ({0})",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT": "Cliquez sur une zone pour la remplir. Garder Ctrl appuyé pour prendre en compte toutes les couches.",
+  "FLOOD_FILL_TOOL_ACTION_DISPLAY_CTRL": "Cliquez sur une zone pour la remplir. Maintenir contrôle Ctrl pour ne considérer que les calques courants.",
+  "LASSO_TOOL_TOOLTIP": "Lasso. ({0})",
+  "LASSO_TOOL_ACTION_DISPLAY_DEFAULT": "Cliquez et déplacez pour sélectionner les pixels à l'intérieur du lasso. Maintenez Shift pour ajouter à la sélection existante. Maintenez Ctrl pour enlever de la sélection existante.",
+  "LASSO_TOOL_ACTION_DISPLAY_SHIFT": "Cliquez et déplacez pour ajouter les pixels à l'intérieur du lasso à la sélection.",
+  "LASSO_TOOL_ACTION_DISPLAY_CTRL": "Cliquez et déplacez pour retirer les pixels à l'intérieur du lasso à la sélection.",
+  "LINE_TOOL_TOOLTIP": "Dessine une ligne sur le canevas ({0}). Maintenir Shift pour activer le calage.",
+  "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Cliquer et déplacer pour dessiner une ligne. Maintenir Shift activer le calage.",
+  "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Cliquer et déplacer la souris pour dessiner une ligne avec le calage activé.",
+  "MAGIC_WAND_TOOL_TOOLTIP": "Baguette Magique ({0}). Remplit la sélection",
+  "MAGIC_WAND_ACTION_DISPLAY": "Cliquer pour remplir la sélection.",
+  "PEN_TOOL": "Stylo",
+  "BRIGHTNESS_TOOL": "Luminosité",
+  "COLOR_PICKER_TOOL": "Pipette",
+  "ELLIPSE_TOOL": "Ellipse",
+  "ERASER_TOOL": "Gomme",
+  "FLOOD_FILL_TOOL": "Remplissage",
+  "LASSO_TOOL": "Lasso",
+  "LINE_TOOL": "Ligne",
+  "MAGIC_WAND_TOOL": "Baguette magique",
+  "MOVE_TOOL": "Déplacer",
+  "MOVE_VIEWPORT_TOOL": "Déplacer l'affichage",
+  "RECTANGLE_TOOL": "Rectangle",
+  "ROTATE_VIEWPORT_TOOL": "Faire pivoter l'affichage",
+  "SELECT_TOOL_NAME": "Sélectionner",
+  "ZOOM_TOOL": "Zoomer",
+  "SHAPE_LABEL": "Forme",
+  "MODE_LABEL": "Mode",
+  "SCOPE_LABEL": "Portée",
+  "FILL_SHAPE_LABEL": "Forme de remplissage",
+  "FILL_COLOR_LABEL": "Couleur de remplissage",
+  "TOOL_SIZE_LABEL": "Taille de l'outil",
+  "STRENGTH_LABEL": "Intensité",
+  "NEW": "Nouveau",
+  "ADD": "Ajouter",
+  "SUBTRACT": "Soustraire",
+  "INTERSECT": "Croiser",
+  "RECTANGLE": "Rectangle",
+  "CIRCLE": "Cercle",
+  "ABOUT": "À propos",
+  "MINIMIZE": "Minimiser",
+  "RESTORE": "Restaurer",
+  "MAXIMIZE": "Maximiser",
+  "CLOSE": "Fermer",
+  "EXPORT_SIZE_HINT": "Si vous souhaitez partager l'image, essayer {0}% pour une meilleure clarté",
+  "CREATE": "Créer",
+  "BASE_LAYER_NAME": "Couche de base",
+  "ENABLE_MASK": "Activer le masque",
+  "SELECTED_AREA_EMPTY": "La zone sélectionnée est vide",
+  "NOTHING_TO_COPY": "Rien à copier",
+  "REFERENCE_LAYER_PATH": "Chemin de la couche de référence",
+  "FLIP": "Retourner",
+  "ROTATION": "Rotation",
+  "ROT_IMG_90_D": "Pivoter l'image de 90°",
+  "ROT_IMG_180_D": "Pivoter l'image de 180°",
+  "ROT_IMG_-90_D": "Pivoter l'image de -90°",
+  "ROT_LAYERS_90_D": "Pivoter les couches sélectionnées de 90°",
+  "ROT_LAYERS_180_D": "Pivoter les couches sélectionnées de 180°",
+  "ROT_LAYERS_-90_D": "Pivoter les couches sélectionnées de -90°",
+  "UNNAMED_PALETTE": "Palette sans nom",
+  "CLICK_SELECT_PRIMARY": "Cliquez pour sélectionner en tant que couleur principale.",
+  "PEN_MODE": "Mode stylo",
+  "VIEW": "Vue",
+  "HORIZONTAL_LINE_SYMMETRY": "Ligne de symétrie horizontale",
+  "VERTICAL_LINE_SYMMETRY": "Ligne de symétrie verticale",
+  "COLOR_PICKER_TITLE": "Pipette",
+  "COLOR_SLIDERS_TITLE": "Curseurs de couleurs",
+  "PALETTE_TITLE": "Palette",
+  "SWATCHES_TITLE": "Échantillons",
+  "LAYERS_TITLE": "Couches",
+  "NORMAL_BLEND_MODE": "Normal",
+  "DARKEN_BLEND_MODE": "Assombrir",
+  "MULTIPLY_BLEND_MODE": "Multiplier",
+  "COLOR_BURN_BLEND_MODE": "Brûlure de couleur",
+  "LIGHTEN_BLEND_MODE": "Éclaircir",
+  "SCREEN_BLEND_MODE": "Filtrer",
+  "COLOR_DODGE_BLEND_MODE": "Esquive de couleur",
+  "OVERLAY_BLEND_MODE": "Recouvrir",
+  "SOFT_LIGHT_BLEND_MODE": "Lumière douce",
+  "HARD_LIGHT_BLEND_MODE": "Lumière forte",
+  "DIFFERENCE_BLEND_MODE": "Différence",
+  "EXCLUSION_BLEND_MODE": "Exclusion",
+  "HUE_BLEND_MODE": "Teinte",
+  "SATURATION_BLEND_MODE": "Saturation",
+  "LUMINOSITY_BLEND_MODE": "Luminosité",
+  "COLOR_BLEND_MODE": "Couleur",
+  "NOT_SUPPORTED_BLEND_MODE": "Non supporté",
+  "RESTART": "Recommencer",
+  "SORT_BY": "Trier par",
+  "NAME": "Nom",
+  "COLORS": "Couleurs",
+  "DEFAULT": "Défaut",
+  "ALPHABETICAL": "Alphabétique",
+  "COLOR_COUNT": "Nombre de couleurs",
+  "ANY": "N'importe quel",
+  "MAX": "Maximum",
+  "MIN": "Minimum",
+  "EXACT": "Exact",
+  "ASCENDING": "Croissant",
+  "DESCENDING": "Décroissant",
+  "NAME_IS_TOO_LONG": "Le nom est trop long",
+  "STOP_IT_TEXT1": "Ça suffit. Rangez vos noms de fichiers.",
+  "STOP_IT_TEXT2": "Pourriez vous, s'il vous plaît, arrêter de copier ces noms ?",
+  "CLICK_TO_CHOOSE_COLOR": "Cliquez pour choisir une couleur",
+  "REPLACE_COLOR": "Remplacer la couleur",
+  "PALETTE_COLOR_TOOLTIP": "Cliquez pour sélectionner en tant que couleur principale. Glissez et déposez sur une autre palette pour les échanger.",
+  "ADD_FROM_SWATCHES": "Ajouter depuis les échantillons",
+  "ADD_COLOR_TO_PALETTE": "Ajouter la couleur à la palette",
+  "USE_IN_CURRENT_IMAGE": "Utiliser dans l'image courante",
+  "ADD_TO_FAVORITES": "Ajouter aux favoris",
+  "BROWSE_PALETTES": "Naviguer les palettes",
+  "LOAD_PALETTE": "Charger une palette",
+  "SAVE_PALETTE": "Sauvegarder la palette",
+  "FAVORITES": "Favoris",
+  "ADD_FROM_CURRENT_PALETTE": "Ajouter depuis la palette actuelle",
+  "OPEN_PALETTES_DIR_TOOLTIP": "Ouvrir le répertoire des palettes depuis l'explorateur",
+  "BROWSE_ON_LOSPEC_TOOLTIP": "Naviguer les palettes sur Lospec",
+  "IMPORT_FROM_FILE_TOOLTIP": "Importer depuis un fichier",
+  "TOP_LEFT": "Haut gauche",
+  "TOP_CENTER": "Haut centre",
+  "TOP_RIGHT": "Haut droite",
+  "MIDDLE_LEFT": "Milieu gauche",
+  "MIDDLE_CENTER": "Milieu centre",
+  "MIDDLE_RIGHT": "Milieu droite",
+  "BOTTOM_LEFT": "Bas gauche",
+  "BOTTOM_CENTER": "Bas centre",
+  "BOTTOM_RIGHT": "Bas droite",
+  "CLIP_TO_BELOW": "Fixer au membre d'en dessous",
+  "MOVE_UPWARDS": "Se déplacer en haut",
+  "MOVE_DOWNWARDS": "Se déplacer vers le bas",
+  "MERGE_SELECTED": "Fusion séléctionnée",
+  "LOCK_TRANSPARENCY": "Verrouiller la transparence",
+  "COULD_NOT_LOAD_PALETTE": "Impossible de charger les palettes",
+  "NO_PALETTES_FOUND": "Aucune palette trouvée.",
+  "LOSPEC_LINK_TEXT": "J'ai entendu dire que l'on pouvait en trouver ici : lospec.com/palette_list",
+  "PALETTE_BROWSER": "Navigateur de palettes",
+  "DELETE_PALETTE_CONFIRMATION": "Êtes-vous sûr de vouloir supprimer cette palette ? Cette action est irréversible.",
+  "SHORTCUTS_IMPORTED": "Les raccourcis de {0} ont été importés avec succès.",
+  "SHORTCUT_PROVIDER_DETECTED": "Nous avons détecter que {0} est installé. Devons-nous en importer des raccourcis ?",
+  "IMPORT_INSTALLATION_OPTION1": "Importer depuis une installation",
+  "IMPORT_INSTALLATION_OPTION2": "Utiliser les paramètres par défaut",
+  "IMPORT_FROM_TEMPLATE": "Importer depuis un modèle",
+  "SHORTCUTS_IMPORTED_SUCCESS": "Raccourcis importés avec succès.",
+  "WARNING_RESET_SHORTCUTS_DEFAULT": "Êtes-vous sûr de vouloir réinitialiser tous les raccourcis à leur valeur initiale ?",
+  "SUCCESS": "Succès",
+  "WARNING": "Attention",
+  "ERROR_IMPORTING_IMAGE": "Une erreur est survenue lors de l'import de l'image.",
+  "SHORTCUTS_CORRUPTED_TITLE": "Fichier de raccourcis corrompu",
+  "SHORTCUTS_CORRUPTED": "Fichier de raccourcis corrompu, rétablissement des valeurs par défaut.",
+  "FAILED_DOWNLOAD_PALETTE": "Echec de chargement de la palette",
+  "FILE_INCORRECT_FORMAT": "Ce fichier n'était pas au bon format",
+  "INVALID_FILE": "Fichier invalide",
+  "SHORTCUTS_FILE_INCORRECT_FORMAT": "Le fichier de raccourcis n'était pas au bon format",
+  "UNSUPPORTED_FILE_FORMAT": "Ce type de fichier n'est pas supporté",
+  "ALREADY_ASSIGNED": "Déjà assigné",
+  "REPLACE": "Remplacer",
+  "SWAP": "Échanger",
+  "SHORTCUT_ALREADY_ASSIGNED_SWAP": "Ce raccourci est déjà assigné à '{0}'\nVoulez-vous remplacer le raccourci existant ou échanger les deux ?",
+  "SHORTCUT_ALREADY_ASSIGNED_OVERWRITE": "Ce raccourci est déjà assigné à '{0}'\nVoulez-vous remplacer le raccourci existant ?",
+  "UNSAVED_CHANGES": "Changements non sauvegardés",
+  "DOCUMENT_MODIFIED_SAVE": "Ce document a été modifié. Souhaitez-vous sauvegarder les modifications ?",
+  "PROJECT_MAINTAINERS": "Responsables de projet",
+  "OTHER_AWESOME_CONTRIBUTORS": "Et d'autres contributeurs géniaux",
+  "HELP": "Aide",
+  "STOP_IT_TEXT3": "Sérieusement, arrêtez.",
+  "STOP_IT_TEXT4": "Vous n'avez rien de mieux à faire ?",
+  "PRESS_ANY_KEY": "Appuyez sur n'importe quelle touche",
+  "NONE_SHORTCUT": "Aucun",
+  "REFERENCE": "Référence",
+  "PUT_REFERENCE_LAYER_ABOVE": "Mettre la couche de référence au dessus",
+  "PUT_REFERENCE_LAYER_BELOW": "Mettre la couche de référence en dessous",
+  "TOGGLE_VERTICAL_SYMMETRY": "Activer/Désactiver la symétrie verticale",
+  "TOGGLE_HORIZONTAL_SYMMETRY": "Activer/Désactiver la symétrie horizontale",
+  "RESET_VIEWPORT": "Réinitialiser l'affichage",
+  "MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING": "Cliquez et maintenez pour déplacer les pixels dans les couches sélectionnés.",
+  "CTRL_KEY": "Ctrl",
+  "SHIFT_KEY": "Shift",
+  "ALT_KEY": "Alt",
+  "RENAME": "Renommer",
+  "PIXEL_UNIT": "px",
+  "OPEN_LOCALIZATION_DEBUG_WINDOW": "Ouvrir la fenêtre de débogage de localisation",
+  "API_KEY": "Clé d'API",
+  "LOAD_LANGUAGE_FROM_FILE": "Charger une langue depuis un fichier",
+  "LOG_IN": "Se connecter",
+  "SYNC": "Synchroniser",
+  "NOT_LOGGED_IN": "Non connecté",
+  "POE_EDITOR_ERROR": "Erreur POEditor : {0} {1}",
+  "HTTP_ERROR_MESSAGE": "Erreur HTTP : {0} {1}",
+  "LOGGED_IN": "Connecté",
+  "SYNCED_SUCCESSFULLY": "Synchronisé avec succès",
+  "EXCEPTION_ERROR": "Exception : {0}",
+  "DROP_PALETTE": "Déposer la palette ici",
+  "SECURITY_ERROR": "Erreur de sécurité",
+  "SECURITY_ERROR_MSG": "Accès à l'écriture restreint pour l'emplacement spécifié.",
+  "IO_ERROR": "Erreur IO",
+  "IO_ERROR_MSG": "Erreur lors de l'écriture sur le disque.",
+  "COULD_NOT_SAVE_PALETTE": "Il y a eu une erreur lors de la sauvegarde de la palette.",
+  "NO_COLORS_TO_SAVE": "Il n'y a aucune couleur à sauvegarder.",
+  "SINGLE_LAYER": "Couche unique",
+  "CHOOSE": "Choisir",
+  "REMOVE": "Supprimer",
+  "FILE_HAS_INVALID_SHORTCUT": "Le fichier contient un raccourci invalide",
+  "FILE_EXTENSION_NOT_SUPPORTED": "Le type de fichier '{0}' n'est pas pris en charge",
+  "ERROR_READING_FILE": "Erreur lors de la lecture du fichier",
+  "DISCARD_PALETTE_CONFIRMATION": "Êtes-vous sûr d'abandonner la palette actuelle ? Cette action est irréversible.",
+  "IMPORT_AS_NEW_LAYER": "Importer en tant que nouvelle couche",
+  "PASTE_AS_PRIMARY_COLOR": "Coller en tant que couleur principale",
+  "IMPORT_AS_NEW_FILE": "Importer en tant que nouveau fichier",
+  "IMPORT_PALETTE_FILE": "Importer un fichier de palette",
+  "IMPORT_MULTIPLE_PALETTE_COLORS": "Importer des couleurs dans la palette",
+  "IMPORT_SINGLE_PALETTE_COLOR": "Importer une couleur dans la palette",
+  "IMPORT_AS_REFERENCE_LAYER": "Importer en tant que couche de référence",
+  "OPEN_FILE_FROM_CLIPBOARD": "Ouvrir depuis le presse-papier",
+  "OPEN_FILE_FROM_CLIPBOARD_DESCRIPTIVE": "Ouvre depuis le presse-papier",
+  "APPLY": "Appliquer",
+  "UPDATE_SOURCE": "Mettre à jour la source",
+  "COPY_TO_CLIPBOARD": "Copier dans le presse-papier",
+  "SELECT_A_LANGUAGE": "Sélectionner une langue",
+  "DONE": "Fait",
+  "SOURCE_UNSET_OR_MISSING": "Source manquante/non fixée",
+  "SOURCE_NEWER": "Source plus récente",
+  "SOURCE_UP_TO_DATE": "Source à jour",
+  "SOURCE_OLDER": "Cloud plus récent",
+  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Cliquez afin de sélectionner des couleurs depuis la couche de référence.",
+  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Cliquez afin de sélectionner des couleurs depuis le canevas.",
+  "SHORTCUTS_TITLE": "Raccourcis",
+  "OPEN_DOCUMENTATION": "Ouvrir la documentation",
+  "LOCAL_PALETTE_SOURCE_NAME": "Local",
+  "BUY_SUPPORTER_PACK": "Acheter le Pack Supporter",
+  "NEWS": "Nouvelles",
+  "DISABLE_NEWS_PANEL": "Afficher le panneau des Nouvelles dans l'écran de démarrage",
+  "FAILED_FETCH_NEWS": "Échec de la récupération des nouvelles",
+  "CROP_TO_SELECTION": "Recadrer sur la sélection",
+  "CROP_TO_SELECTION_DESCRIPTIVE": "Recadre l'image sur la sélection",
+  "SHOW_CONTEXT_MENU": "Afficher le menu contextuel",
+  "ERASE": "Effacer",
+  "USE_SECONDARY_COLOR": "Utiliser la couleur secondaire",
+  "RIGHT_CLICK_MODE": "Mode clic droit",
+  "ADD_PRIMARY_COLOR_TO_PALETTE": "Ajouter une couleur primaire à une palette",
+  "ADD_PRIMARY_COLOR_TO_PALETTE_DESCRIPTIVE": "Ajoute une couleur primaire à la palette actuelle",
+  "EXPORT_SAVE_TITLE": "Choisissez un emplacement pour sauvegarder l'image",
+  "BROWSE_DIRECTORY": "Parcourir le fichier",
+  "SEND": "Envoyer le rapport",
+  "OPEN_DOCKABLE_MENU": "Ouvrir l'onglet",
+  "TIMELINE_TITLE": "Chronologie",
+  "EXPORT_IMAGE_HEADER": "Image",
+  "EXPORT_ANIMATION_HEADER": "Animation",
+  "EXPORT_SPRITESHEET_HEADER": "Feuille de sprites",
+  "PIXI_FILE": "Fichiers PixiEditor",
+  "PNG_FILE": "Images PNG",
+  "GIF_FILE": "GIFs",
+  "BMP_FILE": "Images BMP",
+  "IMAGE_FILES": "Fichiers Image",
+  "VIDEO_FILES": "Fichiers Vidéo",
+  "MP4_FILE": "Vidéos MP4",
+  "COLUMNS": "Colonnes",
+  "ROWS": "Lignes",
+  "BACKGROUND": "Arrière-plan",
+  "OPACITY": "Opacité",
+  "IS_VISIBLE": "Visible",
+  "BLEND_MODE": "Mode fusion",
+  "MASK": "Masque",
+  "MASK_IS_VISIBLE": "Masque visible",
+  "OUTPUT": "Sortie",
+  "INPUT": "Entrée",
+  "CONTENT": "Contenu",
+  "RADIUS": "Rayon",
+  "STROKE_COLOR": "Couleur du trait",
+  "STROKE_WIDTH": "Épaisseur du trait",
+  "FILL_COLOR": "Remplir avec la couleur",
+  "TOP": "Haut",
+  "BOTTOM": "Bas",
+  "CHANNELS_DOCK_TITLE": "Canaux",
+  "RED": "Rouge",
+  "GREEN": "Vert",
+  "BLUE": "Bleu",
+  "ALPHA": "Alpha",
+  "COLOR": "Couleur",
+  "COORDINATE": "Coordonnées",
+  "VECTOR": "Vecteur",
+  "MATRIX": "Matrice",
+  "TRANSFORMED": "Transformé",
+  "GRAYSCALE": "Niveau de gris",
+  "SIZE": "Taille",
+  "NOISE": "Bruit",
+  "SCALE": "Taille",
+  "SEED": "Graine",
+  "KERNEL": "Noyau",
+  "GAIN": "Gain",
+  "BIAS": "Biais",
+  "TILE_MODE": "Mode Tuile",
+  "OUTPUT_NODE": "Sortie",
+  "NOISE_NODE": "Bruit",
+  "ELLIPSE_NODE": "Ellipse",
+  "CREATE_IMAGE_NODE": "Créer une image",
+  "FOLDER_NODE": "Répertoire",
+  "IMAGE_LAYER_NODE": "Couche Image",
+  "MATH_NODE": "Math",
+  "MERGE_NODE": "Fusionner",
+  "COMBINE_CHANNELS_NODE": "Combiner les Canaux",
+  "COMBINE_COLOR_NODE": "Combiner les Couleurs",
+  "COMBINE_VECD_NODE": "Combiner les Vecteurs",
+  "COMBINE_VECI_NODE": "Combiner les Vecteurs d'Entiers",
+  "SEPARATE_COLOR_NODE": "Séparer les Couleurs",
+  "TIME_NODE": "Temps",
+  "FILTERS": "Filtres",
+  "PREVIOUS": "Précédent",
+  "FILL": "Remplir",
+  "MATH_MODE": "Mode Math",
+  "NOISE_TYPE": "Type de Bruit",
+  "OCTAVES": "Octaves",
+  "ACTIVE_FRAME": "Couche Active",
+  "NORMALIZED_TIME": "Temps Normalisé",
+  "ERASE_BLEND_MODE": "Effacer",
+  "RAW_LAYER_OUTPUT": "Brut",
+  "BETA_ANIMATIONS": "Animations",
+  "SLIME_EXAMPLE": "Slime Animé",
+  "APPLY_FILTER_NODE": "Appliquer le Filtre",
+  "FILTER": "Filtre",
+  "LERP_NODE": "Lerp",
+  "GRAYSCALE_FILTER_NODE": "Filtre en Niveaux de Gris",
+  "FROM": "Depuis",
+  "TO": "Vers",
+  "TIME": "Temps",
+  "RENDERING_FRAME": "Génération de la Frame {0}/{1}",
+  "RENDERING_VIDEO": "Rendu de Vidéo",
+  "FINISHED": "Fini",
+  "GENERATING_SPRITE_SHEET": "Génération de la Feuille de Sprite",
+  "RENDERING_IMAGE": "Image de Rendu",
+  "PROGRESS_POPUP_TITLE": "Progrès",
+  "POINTS": "Points",
+  "MIN_DISTANCE": "Distance Min.",
+  "MAX_POINTS": "Points Max.",
+  "DISTRIBUTE_POINTS": "Distribution des points",
+  "REMOVE_CLOSE_POINTS": "Enlever les points proches",
+  "RASTERIZE_SHAPE": "Rastériser la forme",
+  "MODE": "Mode",
+  "Factor": "Facteur",
+  "NORMALIZE": "Normaliser",
+  "WEIGHT_FACTOR": "Poids",
+  "STARS_EXAMPLE": "Étoiles",
+  "ADD_EMPTY_FRAME": "Ajouter une frame vide",
+  "DUPLICATE_FRAME": "Dupliquer la frame",
+  "DELETE_FRAME": "Enlever la frame",
+  "DEFAULT_MEMBER_NAME": "Nouvel élément",
+  "SELECT_FILE_FORMAT": "Sélectionner le format de fichier",
+  "NEW_PALETTE_FILE": "palette",
+  "ISLAND_EXAMPLE": "Îles",
+  "TOGGLE_ANIMATION": "Activer/Désactiver l'animation",
+  "NEW_FROM_CLIPBOARD": "Nouveau depuis le presse-papier",
+  "OFFSET": "Décalage",
+  "SHAPE": "Forme",
+  "STRUCTURE": "Structure",
+  "NUMBERS": "Nombres",
+  "OPERATIONS": "Opérations",
+  "GENERATION": "Génération",
+  "NUMBER": "Nombre",
+  "ANIMATION": "Animation",
+  "POSITION": "Position",
+  "MATH_ADD": "Ajouter",
+  "MATH_SUBTRACT": "Soustraire",
+  "MULTIPLY": "Multiplier",
+  "DIVIDE": "Diviser",
+  "SIN": "Sin",
+  "COS": "Cos",
+  "TAN": "Tan",
+  "PIXEL_ART_TOOLSET": "Pixel Art",
+  "VECTOR_TOOLSET": "Vecteur",
+  "VECTOR_LAYER": "Couche de Vecteur",
+  "SYNC_WITH_PRIMARY_COLOR_LABEL": "Synchroniser avec la couleur principale",
+  "NEW_ELLIPSE_LAYER_NAME": "Ellipse",
+  "NEW_RECTANGLE_LAYER_NAME": "Rectangle",
+  "NEW_LINE_LAYER_NAME": "Ligne",
+  "PAINT_TOOLSET": "Peinture",
+  "TOGGLE_SNAPPING": "Activer le calage",
+  "VIEWPORT_ROTATION": "Rotation de l'affichage",
+  "CANVAS_POSITION": "Position du canevas",
+  "CANVAS": "Canevas",
+  "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor a cessé de fonctionner de manière inattendue. Nous avons chargé la dernière sauvegarde automatique de vos fichiers.",
+  "OPEN_AUTOSAVES": "Parcourir les sauvegardes automatiques",
+  "AUTOSAVE_SETTINGS_HEADER": "Sauvegarde automatique",
+  "AUTOSAVE_SETTINGS_SAVE_STATE": "Réouvrir les derniers fichiers au démarrage",
+  "AUTOSAVE_SETTINGS_PERIOD": "Fréquence de sauvegarde automatique",
+  "AUTOSAVE_ENABLED": "Sauvegarde automatique activée",
+  "MINUTE_UNIVERSAL": "min",
+  "AUTOSAVE_SETTINGS_SAVE_USER_FILE": "Sauvegarder automatiquement le fichier sélectionné",
+  "PIXEL_PERFECT_OUTLINE_TYPE": "Pixel perfect",
+  "AUTOSAVE_OPEN_FOLDER": "Ouvrir le dossier des sauvegardes automatiques",
+  "AUTOSAVE_OPEN_FOLDER_DESCRIPTIVE": "Ouvre le dossier de stockage des sauvegardes automatiques",
+  "AUTOSAVE_TOGGLE_DESCRIPTIVE": "Active/désactive la sauvegarde automatique",
+  "TEXT_TOOL_ACTION_DISPLAY": "Cliquez sur le canvas pour ajouter un nouveau texte (glissez en cliquant pour définir la taille). Cliquez sur un texte existant pour le modifier."
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio