diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 579efe8a4..84033e301 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -92,12 +92,12 @@ jobs: framework: net8.0 sdk: 8.0.x runtime: -x64 - codecov: true + codecov: false - os: macos-26 framework: net8.0 sdk: 8.0.x runtime: -x64 - codecov: false + codecov: true - os: windows-latest framework: net8.0 sdk: 8.0.x diff --git a/ImageSharp.Drawing.sln b/ImageSharp.Drawing.sln index 74e8e1549..c25885dcd 100644 --- a/ImageSharp.Drawing.sln +++ b/ImageSharp.Drawing.sln @@ -337,28 +337,92 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Drawing.WebGPU", "src\ImageSharp.Drawing.WebGPU\ImageSharp.Drawing.WebGPU.csproj", "{061582C2-658F-40AE-A978-7D74A4EB2C0A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebGPUWindowDemo", "samples\WebGPUWindowDemo\WebGPUWindowDemo.csproj", "{2541FDCD-78AC-40DB-B5E3-6A715DC132BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x64.Build.0 = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.ActiveCfg = Debug|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Debug|x86.Build.0 = Debug|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|Any CPU.Build.0 = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.ActiveCfg = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x64.Build.0 = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.ActiveCfg = Release|Any CPU + {2E33181E-6E28-4662-A801-E2E7DC206029}.Release|x86.Build.0 = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.ActiveCfg = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.ActiveCfg = Release|Any CPU + {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.Build.0 = Release|Any CPU {59804113-1DD4-4F80-8D06-35FF71652508}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59804113-1DD4-4F80-8D06-35FF71652508}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59804113-1DD4-4F80-8D06-35FF71652508}.Debug|x64.ActiveCfg = Debug|Any CPU + {59804113-1DD4-4F80-8D06-35FF71652508}.Debug|x64.Build.0 = Debug|Any CPU + {59804113-1DD4-4F80-8D06-35FF71652508}.Debug|x86.ActiveCfg = Debug|Any CPU + {59804113-1DD4-4F80-8D06-35FF71652508}.Debug|x86.Build.0 = Debug|Any CPU {59804113-1DD4-4F80-8D06-35FF71652508}.Release|Any CPU.ActiveCfg = Release|Any CPU {59804113-1DD4-4F80-8D06-35FF71652508}.Release|Any CPU.Build.0 = Release|Any CPU + {59804113-1DD4-4F80-8D06-35FF71652508}.Release|x64.ActiveCfg = Release|Any CPU + {59804113-1DD4-4F80-8D06-35FF71652508}.Release|x64.Build.0 = Release|Any CPU + {59804113-1DD4-4F80-8D06-35FF71652508}.Release|x86.ActiveCfg = Release|Any CPU + {59804113-1DD4-4F80-8D06-35FF71652508}.Release|x86.Build.0 = Release|Any CPU {5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|x64.ActiveCfg = Debug|Any CPU + {5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|x64.Build.0 = Debug|Any CPU + {5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|x86.ActiveCfg = Debug|Any CPU + {5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|x86.Build.0 = Debug|Any CPU {5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.ActiveCfg = Release|Any CPU {5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.Build.0 = Release|Any CPU + {5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|x64.ActiveCfg = Release|Any CPU + {5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|x64.Build.0 = Release|Any CPU + {5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|x86.ActiveCfg = Release|Any CPU + {5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|x86.Build.0 = Release|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Debug|x64.ActiveCfg = Debug|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Debug|x64.Build.0 = Debug|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Debug|x86.ActiveCfg = Debug|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Debug|x86.Build.0 = Debug|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Release|Any CPU.Build.0 = Release|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Release|x64.ActiveCfg = Release|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Release|x64.Build.0 = Release|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Release|x86.ActiveCfg = Release|Any CPU + {061582C2-658F-40AE-A978-7D74A4EB2C0A}.Release|x86.Build.0 = Release|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Debug|x64.Build.0 = Debug|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Debug|x86.Build.0 = Debug|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|Any CPU.Build.0 = Release|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|x64.ActiveCfg = Release|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|x64.Build.0 = Release|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|x86.ActiveCfg = Release|Any CPU + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -386,11 +450,14 @@ Global {68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {5493F024-0A3F-420C-AC2D-05B77A36025B} = {528610AC-7C0C-46E8-9A2D-D46FD92FEE29} {23859314-5693-4E6C-BE5C-80A433439D2A} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} + {061582C2-658F-40AE-A978-7D74A4EB2C0A} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} + {2541FDCD-78AC-40DB-B5E3-6A715DC132BA} = {528610AC-7C0C-46E8-9A2D-D46FD92FEE29} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution + shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{061582c2-658f-40ae-a978-7d74a4eb2c0a}*SharedItemsImports = 5 shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2e33181e-6e28-4662-a801-e2e7dc206029}*SharedItemsImports = 5 shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13 EndGlobalSection diff --git a/README.md b/README.md index b5a3413ee..13bd490c0 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,10 @@ SixLabors.ImageSharp.Drawing [![Build Status](https://img.shields.io/github/actions/workflow/status/SixLabors/ImageSharp.Drawing/build-and-test.yml?branch=main)](https://github.com/SixLabors/ImageSharp.Drawing/actions) [![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp.Drawing/branch/main/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp.Drawing) [![License: Six Labors Split](https://img.shields.io/badge/license-Six%20Labors%20Split-%23e30183)](https://github.com/SixLabors/ImageSharp.Drawing/blob/main/LICENSE) -[![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=flat&logo=twitter)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) -### **ImageSharp.Drawing** provides extensions to ImageSharp containing powerful, cross-platform 2D polygon manipulation and drawing APIs. - -Designed to democratize image processing, ImageSharp.Drawing brings you an incredibly powerful yet beautifully simple API. - -Built against [.NET 6](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp.Drawing can be used in device, cloud, and embedded/IoT scenarios. +**ImageSharp.Drawing** is a cross-platform 2D drawing library built on top of [ImageSharp](https://github.com/SixLabors/ImageSharp). It provides path construction, polygon manipulation, fills, strokes, gradient brushes, pattern brushes, and text rendering. Built against [.NET 8](https://docs.microsoft.com/en-us/dotnet/standard/net-standard). ## License @@ -61,12 +56,12 @@ If you prefer, you can compile ImageSharp.Drawing yourself (please do and help!) - Using [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) - Make sure you have the latest version installed - - Make sure you have [the .NET 7 SDK](https://www.microsoft.com/net/core#windows) installed + - Make sure you have [the .NET 8 SDK](https://www.microsoft.com/net/core#windows) installed Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**: - [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) -- [the .NET 7 SDK](https://www.microsoft.com/net/core#linuxubuntu) +- [the .NET 8 SDK](https://www.microsoft.com/net/core#linuxubuntu) To clone ImageSharp.Drawing locally, click the "Clone in [YOUR_OS]" button above or run the following git commands: diff --git a/samples/DrawShapesWithImageSharp/ImageSharpLogo.cs b/samples/DrawShapesWithImageSharp/ImageSharpLogo.cs deleted file mode 100644 index fd92011bc..000000000 --- a/samples/DrawShapesWithImageSharp/ImageSharpLogo.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Drawing; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.Shapes.DrawShapesWithImageSharp; - -public static class ImageSharpLogo -{ - public static void SaveLogo(float size, string path) - { - // the point are based on a 1206x1206 shape so size requires scaling from there - float scalingFactor = size / 1206; - - Vector2 center = new(603); - - // segment whose center of rotation should be - Vector2 segmentOffset = new(301.16968f, 301.16974f); - IPath segment = new Polygon( - new LinearLineSegment(new Vector2(230.54f, 361.0261f), new Vector2(5.8641942f, 361.46031f)), - new CubicBezierLineSegment( - new Vector2(5.8641942f, 361.46031f), - new Vector2(-11.715693f, 259.54052f), - new Vector2(24.441609f, 158.17478f), - new Vector2(78.26f, 97.0461f))).Translate(center - segmentOffset); - - // we need to create 6 of theses all rotated about the center point - List segments = []; - for (int i = 0; i < 6; i++) - { - float angle = i * ((float)Math.PI / 3); - IPath s = segment.Transform(Matrix3x2.CreateRotation(angle, center)); - segments.Add(s); - } - - List colors = - [ - Color.ParseHex("35a849"), - Color.ParseHex("fcee21"), - Color.ParseHex("ed7124"), - Color.ParseHex("cb202d"), - Color.ParseHex("5f2c83"), - Color.ParseHex("085ba7") - ]; - - Matrix3x2 scaler = Matrix3x2.CreateScale(scalingFactor, Vector2.Zero); - - int dimensions = (int)Math.Ceiling(size); - using (Image img = new(dimensions, dimensions)) - { - img.Mutate(i => i.Fill(Color.Black)); - img.Mutate(i => i.Fill(Color.ParseHex("e1e1e1ff"), new EllipsePolygon(center, 600f).Transform(scaler))); - img.Mutate(i => i.Fill(Color.White, new EllipsePolygon(center, 600f - 60).Transform(scaler))); - - for (int s = 0; s < 6; s++) - { - img.Mutate(i => i.Fill(colors[s], segments[s].Transform(scaler))); - } - - img.Mutate(i => i.Fill(Color.FromPixel(new Rgba32(0, 0, 0, 170)), new ComplexPolygon(new EllipsePolygon(center, 161f), new EllipsePolygon(center, 61f)).Transform(scaler))); - - string fullPath = System.IO.Path.GetFullPath(System.IO.Path.Combine("Output", path)); - - img.Save(fullPath); - } - } -} diff --git a/samples/DrawShapesWithImageSharp/Program.cs b/samples/DrawShapesWithImageSharp/Program.cs index 04497dc93..c602d3cf3 100644 --- a/samples/DrawShapesWithImageSharp/Program.cs +++ b/samples/DrawShapesWithImageSharp/Program.cs @@ -21,8 +21,6 @@ public static void Main(string[] args) { OutputClippedRectangle(); OutputStars(); - - ImageSharpLogo.SaveLogo(300, "ImageSharp.png"); } private static void OutputStars() @@ -113,7 +111,7 @@ private static void DrawSerializedOPenSansLetterShape_a() })]; ComplexPolygon complex = new(polys); - complex.SaveImage("letter", "a.png"); + complex.SaveImage("Letter", "a.png"); } private static void DrawSerializedOPenSansLetterShape_o() @@ -131,7 +129,7 @@ private static void DrawSerializedOPenSansLetterShape_o() })]; ComplexPolygon complex = new(polys); - complex.SaveImage("letter", "o.png"); + complex.SaveImage("Letter", "o.png"); } private static void DrawOval() @@ -159,7 +157,7 @@ private static void OutputDrawnShape() sb.AddLine(new Vector2(25, 30), new Vector2(15, 30)); sb.CloseFigure(); - sb.Build().Translate(0, 10).Scale(10).SaveImage("drawing", $"paths.png"); + sb.Build().Translate(0, 10).Scale(10).SaveImage("Drawing", $"paths.png"); } private static void OutputDrawnShapeHourGlass() @@ -176,7 +174,7 @@ private static void OutputDrawnShapeHourGlass() sb.AddLine(new Vector2(15, 30), new Vector2(25, 30)); sb.CloseFigure(); - sb.Build().Translate(0, 10).Scale(10).SaveImage("drawing", $"HourGlass.png"); + sb.Build().Translate(0, 10).Scale(10).SaveImage("Drawing", $"HourGlass.png"); } private static void OutputStarOutline(int points, float inner = 10, float outer = 20, float width = 5, LineJoin jointStyle = LineJoin.Miter) @@ -239,11 +237,12 @@ public static void SaveImage(this IPathCollection collection, params string[] pa int height = (int)(collection.Bounds.Top + collection.Bounds.Bottom); using Image img = new(width, height); - // Fill the canvas background and draw our shape - img.Mutate(i => i.Fill(Color.DarkBlue)); - - // Draw our path collection. - img.Mutate(i => i.Fill(Color.HotPink, collection)); + img.Mutate(i => i.ProcessWithCanvas(canvas => + { + // Fill the canvas background and draw our shape. + canvas.Fill(Brushes.Solid(Color.DarkBlue)); + canvas.Fill(Brushes.Solid(Color.HotPink), collection); + })); // Ensure directory exists string fullPath = IOPath.GetFullPath(IOPath.Combine("Output", IOPath.Combine(path))); @@ -264,11 +263,15 @@ public static void SaveImageWithPath(this IPathCollection collection, IPath shap using Image img = new(width, height); - // Fill the canvas background and draw our shape - img.Mutate(i => i.Fill(Color.DarkBlue).Fill(Color.White.WithAlpha(.25F), shape)); + img.Mutate(i => i.ProcessWithCanvas(canvas => + { + // Fill the canvas background and draw our shape. + canvas.Fill(Brushes.Solid(Color.DarkBlue)); + canvas.Fill(Brushes.Solid(Color.White.WithAlpha(.25F)), shape); - // Draw our path collection. - img.Mutate(i => i.Fill(Color.HotPink, collection)); + // Draw our path collection. + canvas.Fill(Brushes.Solid(Color.HotPink), collection); + })); // Ensure directory exists string fullPath = IOPath.GetFullPath(IOPath.Combine("Output", IOPath.Combine(path))); @@ -282,8 +285,11 @@ public static void SaveImage(this IPath shape, int width, int height, params str public static void SaveImage(this IPathCollection shape, int width, int height, params string[] path) { using Image img = new(width, height); - img.Mutate(i => i.Fill(Color.DarkBlue)); - img.Mutate(i => i.Fill(Color.HotPink, shape)); + img.Mutate(i => i.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.DarkBlue)); + canvas.Fill(Brushes.Solid(Color.HotPink), shape); + })); // Ensure directory exists string fullPath = IOPath.GetFullPath(IOPath.Combine("Output", IOPath.Combine(path))); diff --git a/samples/DrawShapesWithImageSharp/README.md b/samples/DrawShapesWithImageSharp/README.md new file mode 100644 index 000000000..1d1bd9c82 --- /dev/null +++ b/samples/DrawShapesWithImageSharp/README.md @@ -0,0 +1,21 @@ +# Draw Shapes With ImageSharp + +A sample application that demonstrates the core vector drawing capabilities of ImageSharp.Drawing. Each example renders shapes to an `Image` using the `DrawingCanvas` API and saves the result as a PNG file in the `Output/` directory. + +## What it demonstrates + +- **Stars** — Filled and outlined star polygons with varying point counts, inner/outer radii, line join styles (miter, round, bevel), and dashed outlines with different line caps. +- **Clipping** — Rectangle-on-rectangle clipping using `IPath.Clip()`. +- **Path building** — Constructing complex shapes with `PathBuilder`, including multi-figure paths (a V overlaid with a rectangle) and an hourglass shape. +- **Curves** — Ellipses via `EllipsePolygon` and cubic Bezier arcs via `CubicBezierLineSegment`. +- **Text as paths** — Converting text to vector outlines using `TextBuilder.GeneratePaths()` with system fonts, including text laid out along a curved path. +- **Serialized glyph data** — Rendering OpenSans letter shapes ('a' and 'o') from serialized coordinate data as `ComplexPolygon` instances. +- **Canvas API** — `Fill` for solid backgrounds and shape rendering via `ProcessWithCanvas`. + +## Running + +```bash +dotnet run --project samples/DrawShapesWithImageSharp -c Debug +``` + +Output images are written to the `Output/` directory, organized into subdirectories by category: `Stars/`, `Clipping/`, `Curves/`, `Text/`, `Drawing/`, `Letter/`, and `Issues/`. diff --git a/samples/WebGPUWindowDemo/Program.cs b/samples/WebGPUWindowDemo/Program.cs new file mode 100644 index 000000000..9997e5f5d --- /dev/null +++ b/samples/WebGPUWindowDemo/Program.cs @@ -0,0 +1,464 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using Silk.NET.Core.Native; +using Silk.NET.Maths; +using Silk.NET.WebGPU; +using Silk.NET.Windowing; +using SixLabors.Fonts; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Drawing; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Processing.Backends; +using SixLabors.ImageSharp.Drawing.Text; +using SixLabors.ImageSharp.PixelFormats; +using Color = SixLabors.ImageSharp.Color; +using Rectangle = SixLabors.ImageSharp.Rectangle; + +namespace WebGPUWindowDemo; + +/// +/// Demonstrates the ImageSharp.Drawing WebGPU backend rendering directly to a native +/// swap chain surface. Bouncing ellipses and vertically scrolling text are composited +/// each frame using the API backed by a WebGPU +/// compute compositor. +/// +public static unsafe class Program +{ + private const int WindowWidth = 800; + private const int WindowHeight = 600; + private const int BallCount = 50; + + // Silk.NET WebGPU API and windowing handles. + private static WebGPU wgpu; + private static IWindow window; + + // WebGPU device-level handles. + private static Instance* instance; + private static Surface* surface; + private static SurfaceConfiguration surfaceConfiguration; + private static Adapter* adapter; + private static Device* device; + private static Queue* queue; + + // ImageSharp.Drawing backend and configuration. + private static WebGPUDrawingBackend backend; + private static Configuration drawingConfiguration; + + // Bouncing ball simulation state. + private static Ball[] balls; + private static readonly Random Rng = new(42); + + // FPS counter state. + private static int frameCount; + private static double fpsElapsed; + + // Scrolling text state — glyph geometry is built once at startup via TextBuilder + // and translated vertically each frame. Only glyphs whose bounds intersect the + // visible viewport are submitted for rasterization. + private static IPathCollection scrollPaths; + private static float scrollOffset; + private static float scrollTextHeight; + private const string ScrollText = + "ImageSharp.Drawing on WebGPU\n\n" + + "Real-time GPU-accelerated 2D vector graphics " + + "rendered directly to a native swap chain surface.\n\n" + + "The canvas API provides a familiar drawing model: " + + "Fill, Draw, DrawText, Clip, and Transform — " + + "all composited on the GPU via compute shaders.\n\n" + + "Text is shaped once using SixLabors.Fonts and " + + "converted to vector paths via TextBuilder. " + + "Each frame simply translates the cached geometry.\n\n" + + "Shapes are rasterized into coverage masks on the " + + "CPU, uploaded to GPU textures, then composited " + + "using a WebGPU compute pipeline that evaluates " + + "Porter-Duff blending per pixel.\n\n" + + "The drawing backend automatically manages texture " + + "atlases, bind groups, and pipeline state. It falls " + + "back to the CPU backend for unsupported pixel " + + "formats or when no GPU device is available.\n\n" + + "SixLabors ImageSharp.Drawing\n" + + "github.com/SixLabors/ImageSharp.Drawing\n\n" + + "Built with Silk.NET WebGPU bindings.\n" + + "Running on your GPU right now."; + + public static void Main() + { + // Create a window with no built-in graphics API — we manage WebGPU ourselves. + WindowOptions options = WindowOptions.Default; + options.API = GraphicsAPI.None; + options.Size = new Vector2D(WindowWidth, WindowHeight); + options.Title = "ImageSharp.Drawing WebGPU Demo"; + options.ShouldSwapAutomatically = false; + options.IsContextControlDisabled = true; + + window = Window.Create(options); + window.Load += OnLoad; + window.Update += OnUpdate; + window.Render += OnRender; + window.Closing += OnClosing; + window.FramebufferResize += OnFramebufferResize; + + window.Run(); + } + + /// + /// Called once when the window is ready. Bootstraps the WebGPU device, configures + /// the swap chain, initializes the ImageSharp.Drawing backend, pre-builds the + /// scrolling text geometry, and seeds the ball simulation. + /// + private static void OnLoad() + { + // Bootstrap WebGPU: instance → surface → adapter → device → queue. + wgpu = WebGPU.GetApi(); + + InstanceDescriptor instanceDescriptor = default; + instance = wgpu.CreateInstance(&instanceDescriptor); + + surface = window.CreateWebGPUSurface(wgpu, instance); + + // Request an adapter compatible with our window surface. + RequestAdapterOptions adapterOptions = new() + { + CompatibleSurface = surface + }; + + wgpu.InstanceRequestAdapter( + instance, + ref adapterOptions, + new PfnRequestAdapterCallback((_, a, _, _) => adapter = a), + null); + + Console.WriteLine($"Adapter: 0x{(nuint)adapter:X}"); + + // Request a device with Bgra8UnormStorage — required by the compute compositor + // to write storage textures in Bgra8Unorm format (the swap chain format). + FeatureName requiredFeature = FeatureName.Bgra8UnormStorage; + DeviceDescriptor deviceDescriptor = new() + { + DeviceLostCallback = new PfnDeviceLostCallback(DeviceLost), + RequiredFeatureCount = 1, + RequiredFeatures = &requiredFeature, + }; + + wgpu.AdapterRequestDevice( + adapter, + in deviceDescriptor, + new PfnRequestDeviceCallback((_, d, _, _) => device = d), + null); + + wgpu.DeviceSetUncapturedErrorCallback(device, new PfnErrorCallback(UncapturedError), null); + + queue = wgpu.DeviceGetQueue(device); + + Console.WriteLine($"Device: 0x{(nuint)device:X}, Queue: 0x{(nuint)queue:X}"); + + // Configure the swap chain. + ConfigureSwapchain(); + + // Initialize the ImageSharp.Drawing WebGPU backend and attach it to a + // cloned Configuration so it doesn't affect the global default. + backend = new WebGPUDrawingBackend(); + drawingConfiguration = Configuration.Default.Clone(); + drawingConfiguration.SetDrawingBackend(backend); + + // Pre-build scrolling text geometry at the origin. TextBuilder converts the + // shaped text into an IPathCollection of glyph outlines that can be cheaply + // translated each frame without re-shaping or re-building outlines. + Font scrollFont = SystemFonts.CreateFont("Arial", 24); + TextOptions textOptions = new(scrollFont) + { + Origin = new Vector2(WindowWidth / 2f, 0), + WrappingLength = WindowWidth - 80, + HorizontalAlignment = HorizontalAlignment.Center, + LineSpacing = 1.6f, + }; + + scrollPaths = TextBuilder.GeneratePaths(ScrollText, textOptions); + FontRectangle bounds = TextMeasurer.MeasureSize(ScrollText, textOptions); + scrollTextHeight = bounds.Height; + + // Seed the bouncing ball simulation with random positions, velocities, and colors. + balls = new Ball[BallCount]; + for (int i = 0; i < BallCount; i++) + { + balls[i] = Ball.CreateRandom(Rng, WindowWidth, WindowHeight); + } + + Console.WriteLine("WebGPU windowed demo initialized."); + } + + /// + /// Configures (or reconfigures) the swap chain for the current framebuffer size. + /// Uses Bgra8Unorm to match the canvas pixel format. + /// CopyDst is required because the compositor copies from a transient output texture + /// into the swap chain target. TextureBinding is needed for backdrop sampling. + /// + private static void ConfigureSwapchain() + { + surfaceConfiguration = new SurfaceConfiguration + { + Usage = TextureUsage.RenderAttachment | TextureUsage.CopyDst | TextureUsage.TextureBinding, + Format = TextureFormat.Bgra8Unorm, + PresentMode = PresentMode.Fifo, + Device = device, + Width = (uint)window.FramebufferSize.X, + Height = (uint)window.FramebufferSize.Y, + }; + + wgpu.SurfaceConfigure(surface, ref surfaceConfiguration); + } + + /// + /// Reconfigures the swap chain when the window is resized. + /// + private static void OnFramebufferResize(Vector2D size) + { + if (size.X > 0 && size.Y > 0) + { + ConfigureSwapchain(); + } + } + + /// + /// Fixed-timestep update: advances ball positions and the scroll offset. + /// + private static void OnUpdate(double deltaTime) + { + int w = window.FramebufferSize.X; + int h = window.FramebufferSize.Y; + float dt = (float)deltaTime; + + // Integrate ball positions and bounce off walls. + for (int i = 0; i < balls.Length; i++) + { + balls[i].Update(dt, w, h); + } + + // Advance scrolling text vertically (pixels per second). + scrollOffset += 200f * dt; + } + + /// + /// Per-frame render callback. Acquires a swap chain texture, wraps it as a + /// , creates a , + /// draws all content, flushes the GPU composition, and presents. + /// + private static void OnRender(double deltaTime) + { + int w = window.FramebufferSize.X; + int h = window.FramebufferSize.Y; + if (w <= 0 || h <= 0) + { + return; + } + + // Acquire the next swap chain texture from the surface. + SurfaceTexture surfaceTexture; + wgpu.SurfaceGetCurrentTexture(surface, &surfaceTexture); + switch (surfaceTexture.Status) + { + case SurfaceGetCurrentTextureStatus.Timeout: + case SurfaceGetCurrentTextureStatus.Outdated: + case SurfaceGetCurrentTextureStatus.Lost: + wgpu.TextureRelease(surfaceTexture.Texture); + ConfigureSwapchain(); + return; + case SurfaceGetCurrentTextureStatus.OutOfMemory: + case SurfaceGetCurrentTextureStatus.DeviceLost: + throw new InvalidOperationException($"Surface texture error: {surfaceTexture.Status}"); + } + + TextureView* textureView = wgpu.TextureCreateView(surfaceTexture.Texture, null); + + try + { + // Wrap the swap chain texture as a NativeSurface so the drawing backend + // can composite directly into it. The format must match the swap chain + // configuration (Bgra8Unorm) and the canvas pixel type (Bgra32). + NativeSurface nativeSurface = WebGPUNativeSurfaceFactory.Create( + (nint)device, + (nint)queue, + (nint)surfaceTexture.Texture, + (nint)textureView, + WebGPUTextureFormatId.Bgra8Unorm, + w, + h); + + // NativeCanvasFrame exposes only the GPU surface (no CPU region), + // so the backend always takes the GPU composition path. + NativeCanvasFrame frame = new(new Rectangle(0, 0, w, h), nativeSurface); + + // Create a drawing canvas targeting the swap chain frame. + using DrawingCanvas canvas = new(drawingConfiguration, frame, new DrawingOptions()); + + // Clear to a dark background. + canvas.Fill(Brushes.Solid(Color.FromPixel(new Bgra32(30, 30, 40, 255)))); + + // Draw vertically scrolling text behind the balls. + DrawScrollingText(canvas, w, h); + + // Draw each ball as a filled ellipse. + for (int i = 0; i < balls.Length; i++) + { + ref Ball ball = ref balls[i]; + EllipsePolygon ellipse = new(ball.X, ball.Y, ball.Radius); + canvas.Fill(Brushes.Solid(ball.Color), ellipse); + } + + // Flush submits all queued draw operations to the GPU compositor and + // copies the composited result into the swap chain texture. + canvas.Flush(); + } + finally + { + // Present the frame and release per-frame WebGPU resources. + wgpu.SurfacePresent(surface); + wgpu.TextureViewRelease(textureView); + wgpu.TextureRelease(surfaceTexture.Texture); + } + + // Update FPS counter in the window title once per second. + frameCount++; + fpsElapsed += deltaTime; + if (fpsElapsed >= 1.0) + { + window.Title = $"ImageSharp.Drawing WebGPU Demo — {frameCount / fpsElapsed:F1} FPS | GPU: {backend.DiagnosticGpuCompositeCount} Fallback: {backend.DiagnosticFallbackCompositeCount}"; + frameCount = 0; + fpsElapsed = 0; + } + } + + /// + /// Draws the pre-built scrolling text geometry. The full text block scrolls upward + /// and loops when it passes above the window. Each glyph path is bounds-tested + /// against the viewport so only visible glyphs are rasterized. + /// + private static void DrawScrollingText(DrawingCanvas canvas, int w, int h) + { + if (scrollTextHeight <= 0) + { + return; + } + + // Total cycle: text enters from the bottom, scrolls up, exits the top, then loops. + float totalCycle = h + scrollTextHeight; + float wrappedOffset = scrollOffset % totalCycle; + float y = h - wrappedOffset; + + Matrix3x2 translation = Matrix3x2.CreateTranslation(0, y); + RectangleF viewport = new(0, 0, w, h); + Brush textBrush = Brushes.Solid(Color.FromPixel(new Bgra32(70, 70, 100, 255))); + + // Each IPath in scrollPaths is one glyph outline. Skip any whose translated + // bounding box doesn't intersect the visible area. + foreach (IPath path in scrollPaths) + { + RectangleF pathBounds = path.Bounds; + RectangleF translated = new( + pathBounds.X + translation.M31, + pathBounds.Y + translation.M32, + pathBounds.Width, + pathBounds.Height); + + if (!viewport.IntersectsWith(translated)) + { + continue; + } + + canvas.Fill(textBrush, path.Transform(new Matrix4x4(translation))); + } + } + + /// + /// Tears down the drawing backend and releases all WebGPU resources in reverse + /// creation order. + /// + private static void OnClosing() + { + backend.Dispose(); + + wgpu.DeviceRelease(device); + wgpu.AdapterRelease(adapter); + wgpu.SurfaceRelease(surface); + wgpu.InstanceRelease(instance); + wgpu.Dispose(); + } + + /// WebGPU device-lost callback — logs the reason to the console. + private static void DeviceLost(DeviceLostReason reason, byte* message, void* userData) + => Console.WriteLine($"Device lost ({reason}): {SilkMarshal.PtrToString((nint)message)}"); + + /// WebGPU uncaptured error callback — logs validation errors to the console. + private static void UncapturedError(ErrorType type, byte* message, void* userData) + => Console.WriteLine($"WebGPU {type}: {SilkMarshal.PtrToString((nint)message)}"); + + /// + /// A simple bouncing ball with position, velocity, radius, and color. + /// Reflects off the window edges each frame. + /// + private struct Ball + { + public float X; + public float Y; + public float VelocityX; + public float VelocityY; + public float Radius; + public Color Color; + + /// + /// Creates a ball with a random position inside the window bounds, a random + /// velocity between 100-300 px/s in each axis, a random radius between 20-60 px, + /// and a random semi-transparent color. + /// + public static Ball CreateRandom(Random rng, int width, int height) + { + float radius = 20f + (rng.NextSingle() * 40f); + return new Ball + { + X = radius + (rng.NextSingle() * (width - (2 * radius))), + Y = radius + (rng.NextSingle() * (height - (2 * radius))), + VelocityX = (100f + (rng.NextSingle() * 200f)) * (rng.Next(2) == 0 ? -1 : 1), + VelocityY = (100f + (rng.NextSingle() * 200f)) * (rng.Next(2) == 0 ? -1 : 1), + Radius = radius, + Color = Color.FromPixel(new Bgra32( + (byte)(80 + rng.Next(176)), + (byte)(80 + rng.Next(176)), + (byte)(80 + rng.Next(176)), + 200)), + }; + } + + /// + /// Integrates position by velocity and reflects off the window edges. + /// + public void Update(float dt, int width, int height) + { + this.X += this.VelocityX * dt; + this.Y += this.VelocityY * dt; + + if (this.X - this.Radius < 0) + { + this.X = this.Radius; + this.VelocityX = MathF.Abs(this.VelocityX); + } + else if (this.X + this.Radius > width) + { + this.X = width - this.Radius; + this.VelocityX = -MathF.Abs(this.VelocityX); + } + + if (this.Y - this.Radius < 0) + { + this.Y = this.Radius; + this.VelocityY = MathF.Abs(this.VelocityY); + } + else if (this.Y + this.Radius > height) + { + this.Y = height - this.Radius; + this.VelocityY = -MathF.Abs(this.VelocityY); + } + } + } +} diff --git a/samples/WebGPUWindowDemo/README.md b/samples/WebGPUWindowDemo/README.md new file mode 100644 index 000000000..a49b25c68 --- /dev/null +++ b/samples/WebGPUWindowDemo/README.md @@ -0,0 +1,46 @@ +# WebGPU Window Demo + +A real-time sample application that renders directly to a native window swap chain using the ImageSharp.Drawing WebGPU backend. Bouncing ellipses and vertically scrolling text are composited each frame via the `DrawingCanvas` API, with all composition performed by a WebGPU compute pipeline. + +## What it demonstrates + +- **Native surface rendering** — The swap chain texture is wrapped as a `NativeSurface` and passed to the canvas via `ICanvasFrame`. The backend composites directly into the swap chain target without CPU readback. +- **WebGPU bootstrap** — Full Silk.NET WebGPU initialization: instance → surface → adapter → device → queue, with `Bgra8UnormStorage` requested for compute storage writes to `Bgra8Unorm` textures. +- **Pre-built text geometry** — `TextBuilder.GeneratePaths` shapes the text once at startup. Each frame translates the cached `IPathCollection` with a `Matrix3x2` — no re-shaping or re-building of glyph outlines. +- **Viewport culling** — Only glyph paths whose translated bounding boxes intersect the visible window are submitted for rasterization, keeping frame times low even with large text blocks. +- **Canvas API** — `Fill` for solid backgrounds, `Fill(IPath, Brush)` for ellipses and glyph outlines, `Flush` to submit all queued operations to the GPU compositor. + +## Prerequisites + +- .NET 8.0 SDK or later +- A GPU with Vulkan, Metal, or D3D12 support +- The adapter must support the `Bgra8UnormStorage` feature (most desktop GPUs do) + +## Running + +```bash +dotnet run --project samples/WebGPUWindowDemo -c Debug +``` + +An 800×600 window opens showing colored ellipses bouncing off the walls with scrolling descriptive text in the background. The window title displays the current FPS. + +## Architecture + +Each frame follows this sequence: + +1. `SurfaceGetCurrentTexture` — acquire the next swap chain texture +2. `WebGPUNativeSurfaceFactory.Create(...)` — wrap it as a `NativeSurface` +3. `NativeSurfaceOnlyFrame` — wrap as `ICanvasFrame` (GPU path only, no CPU region) +4. `new DrawingCanvas(config, frame, options)` — create the canvas +5. `canvas.Fill(...)` — queue background, text glyphs, and ellipses +6. `canvas.Flush()` — rasterize coverage masks on CPU, upload to GPU, composite via compute shader, copy result to swap chain texture +7. `SurfacePresent()` — present the frame + +## Performance + +| Scenario | FPS (typical) | +|---|---| +| Balls only (no text) | ~120 | +| Balls + scrolling text | 65–120 | + +FPS varies with how much text is currently visible in the viewport. diff --git a/samples/WebGPUWindowDemo/WebGPUWindowDemo.csproj b/samples/WebGPUWindowDemo/WebGPUWindowDemo.csproj new file mode 100644 index 000000000..7869e86da --- /dev/null +++ b/samples/WebGPUWindowDemo/WebGPUWindowDemo.csproj @@ -0,0 +1,36 @@ + + + + portable + Exe + true + + + + + + net8.0;net10.0 + + + + + net8.0 + + + + + + + + + + + + + + + + diff --git a/src/ImageSharp.Drawing.WebGPU/ImageSharp.Drawing.WebGPU.csproj b/src/ImageSharp.Drawing.WebGPU/ImageSharp.Drawing.WebGPU.csproj new file mode 100644 index 000000000..657b3660b --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/ImageSharp.Drawing.WebGPU.csproj @@ -0,0 +1,82 @@ + + + + SixLabors.ImageSharp.Drawing.WebGPU + SixLabors.ImageSharp.Drawing.WebGPU + SixLabors.ImageSharp.Drawing.Processing.Backends + SixLabors.ImageSharp.Drawing.WebGPU + sixlabors.imagesharp.drawing.128.png + LICENSE + https://github.com/SixLabors/ImageSharp.Drawing/ + $(RepositoryUrl) + Image Draw Shape Path Font + An extension to ImageSharp that allows the drawing of images, paths, and text. + Debug;Release + + Exe + true + true + + + false + + + $(WarningsNotAsErrors);8002 + + + + + 1.0 + + + + + enable + Nullable + + + + + + net8.0;net10.0 + + + + + net8.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ImageSharp.Drawing.WebGPU/RemoteExecutor/Program.cs b/src/ImageSharp.Drawing.WebGPU/RemoteExecutor/Program.cs new file mode 100644 index 000000000..d91a0b881 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/RemoteExecutor/Program.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Entry point for child processes spawned by . +/// Dispatches to the requested probe method by name. +/// Adapted from Microsoft.DotNet.RemoteExecutor (MIT license). +/// +internal static class Program +{ + private static int Main(string[] args) + { + if (args.Length < 1) + { + Console.Error.WriteLine("Usage: {0} methodName", typeof(Program).Assembly.GetName().Name); + return -1; + } + + string methodName = args[0]; + + return methodName switch + { + nameof(WebGPUDrawingBackend.ProbeComputePipelineSupport) => WebGPUDrawingBackend.ProbeComputePipelineSupport(), + _ => -1 + }; + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/RemoteExecutor/RemoteExecutor.cs b/src/ImageSharp.Drawing.WebGPU/RemoteExecutor/RemoteExecutor.cs new file mode 100644 index 000000000..f8749dee5 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/RemoteExecutor/RemoteExecutor.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using IOPath = System.IO.Path; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Minimal remote executor that invokes a named method in a child process. +/// The child process entry point () dispatches to +/// the requested method by name — no reflection is used. +/// Adapted from Microsoft.DotNet.RemoteExecutor (MIT license). +/// +internal static class RemoteExecutor +{ + private static readonly string? AssemblyPath; + private static readonly string? HostRunner; + private static readonly string? RuntimeConfigPath; + private static readonly string? DepsJsonPath; + + static RemoteExecutor() + { + if (!IsSupported) + { + return; + } + + string? processFileName = Process.GetCurrentProcess().MainModule?.FileName; + if (processFileName is null) + { + return; + } + + string baseDir = AppContext.BaseDirectory; + string assemblyName = typeof(RemoteExecutor).Assembly.GetName().Name!; + AssemblyPath = IOPath.Combine(baseDir, assemblyName + ".dll"); + if (!File.Exists(AssemblyPath)) + { + return; + } + + HostRunner = processFileName; + string hostName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet"; + + if (!IOPath.GetFileName(HostRunner).Equals(hostName, StringComparison.OrdinalIgnoreCase)) + { + // Walk up from the runtime directory to find the dotnet host executable. + // The runtime directory is typically: + // /shared/Microsoft.NETCore.App// + // so dotnet.exe is 3–4 levels up depending on trailing separator. + string? directory = RuntimeEnvironment.GetRuntimeDirectory(); + for (int i = 0; i < 4 && directory is not null; i++) + { + directory = IOPath.GetDirectoryName(directory); + if (directory is not null) + { + string dotnetExe = IOPath.Combine(directory, hostName); + if (File.Exists(dotnetExe)) + { + HostRunner = dotnetExe; + break; + } + } + } + } + + string runtimeConfigCandidate = IOPath.Combine(baseDir, assemblyName + ".runtimeconfig.json"); + string depsJsonCandidate = IOPath.Combine(baseDir, assemblyName + ".deps.json"); + + RuntimeConfigPath = File.Exists(runtimeConfigCandidate) ? runtimeConfigCandidate : null; + DepsJsonPath = File.Exists(depsJsonCandidate) ? depsJsonCandidate : null; + } + + /// + /// Gets a value indicating whether this remote executor is supported on the current platform. + /// + internal static bool IsSupported { get; } = + !RuntimeInformation.IsOSPlatform(OSPlatform.Create("IOS")) && + !RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID")) && + !RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")) && + !RuntimeInformation.IsOSPlatform(OSPlatform.Create("WASI")) && + Environment.GetEnvironmentVariable("DOTNET_REMOTEEXECUTOR_SUPPORTED") != "0"; + + /// + /// Invokes the specified static method in a child process and returns its exit code. + /// The method name is dispatched by via a switch statement, + /// so no reflection is needed in the child process. + /// + /// A static method returning (the exit code). + /// Maximum time to wait for the child process. + /// The exit code from the child process, or -1 on failure. + internal static int Invoke(Func method, int timeoutMilliseconds = 30_000) + { + if (!IsSupported || AssemblyPath is null || HostRunner is null) + { + return -1; + } + + string methodName = method.Method.Name; + + string args = "exec"; + if (RuntimeConfigPath is not null) + { + args += $" --runtimeconfig \"{RuntimeConfigPath}\""; + } + + if (DepsJsonPath is not null) + { + args += $" --depsfile \"{DepsJsonPath}\""; + } + + args += $" \"{AssemblyPath}\" \"{methodName}\""; + + ProcessStartInfo psi = new() + { + FileName = HostRunner, + Arguments = args, + UseShellExecute = false, + CreateNoWindow = true + }; + + // Remove profiler environment variables from child process. + psi.Environment.Remove("Cor_Profiler"); + psi.Environment.Remove("Cor_Enable_Profiling"); + psi.Environment.Remove("CoreClr_Profiler"); + psi.Environment.Remove("CoreClr_Enable_Profiling"); + + try + { + using Process? process = Process.Start(psi); + if (process is null) + { + return -1; + } + + if (!process.WaitForExit(timeoutMilliseconds)) + { + try + { + process.Kill(); + } + catch + { + // Ignore cleanup errors. + } + + return -1; + } + + return process.ExitCode; + } + catch + { + return -1; + } + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/ComposeLayerComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/ComposeLayerComputeShader.cs new file mode 100644 index 000000000..bb2867cc8 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/ComposeLayerComputeShader.cs @@ -0,0 +1,220 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Text; +using Silk.NET.WebGPU; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// GPU compute shader that composites a source layer texture onto a destination texture +/// using configurable blend mode, alpha composition mode, and opacity. +/// +internal static class ComposeLayerComputeShader +{ + private static readonly object CacheSync = new(); + private static readonly Dictionary ShaderCache = []; + + private static readonly string ShaderTemplate = + """ + struct LayerConfig { + source_width: u32, + source_height: u32, + dest_offset_x: i32, + dest_offset_y: i32, + color_blend_mode: u32, + alpha_composition_mode: u32, + blend_percentage: u32, + _padding: u32, + }; + + @group(0) @binding(0) var source_texture: texture_2d<__TEXEL_TYPE__>; + @group(0) @binding(1) var backdrop_texture: texture_2d<__TEXEL_TYPE__>; + @group(0) @binding(2) var output_texture: texture_storage_2d<__OUTPUT_FORMAT__, write>; + @group(0) @binding(3) var config: LayerConfig; + + __DECODE_TEXEL_FUNCTION__ + + __ENCODE_OUTPUT_FUNCTION__ + + __BLEND_AND_COMPOSE__ + + @compute @workgroup_size(16, 16, 1) + fn cs_main(@builtin(global_invocation_id) gid: vec3) { + // Output coordinates are in local output-texture space. + let out_x = i32(gid.x); + let out_y = i32(gid.y); + + // Destination coordinates map into the full backdrop texture. + let dest_x = out_x + config.dest_offset_x; + let dest_y = out_y + config.dest_offset_y; + + let dest_dims = textureDimensions(backdrop_texture); + if (dest_x < 0 || dest_y < 0 || u32(dest_x) >= dest_dims.x || u32(dest_y) >= dest_dims.y) { + return; + } + + let src_x = out_x; + let src_y = out_y; + if (u32(src_x) >= config.source_width || u32(src_y) >= config.source_height) { + // Outside layer bounds — pass through the backdrop. + let backdrop = decode_texel(__LOAD_BACKDROP__); + let alpha = backdrop.a; + let rgb = unpremultiply(backdrop.rgb, alpha); + __STORE_OUTPUT__ + return; + } + + let backdrop = decode_texel(__LOAD_BACKDROP__); + let source_raw = decode_texel(__LOAD_SOURCE__); + + // Apply layer opacity. + let opacity = bitcast(config.blend_percentage); + let source = vec4(source_raw.rgb, source_raw.a * opacity); + + let result = compose_pixel(backdrop, source, config.color_blend_mode, config.alpha_composition_mode); + let alpha = result.a; + let rgb = unpremultiply(result.rgb, alpha); + __STORE_OUTPUT__ + } + """; + + /// + /// Gets the null-terminated WGSL source for the layer composite shader variant. + /// + public static bool TryGetCode(TextureFormat textureFormat, out byte[] code, out string? error) + { + if (!CompositeComputeShader.TryGetInputSampleType(textureFormat, out _)) + { + code = []; + error = $"Layer composite shader does not support texture format '{textureFormat}'."; + return false; + } + + lock (CacheSync) + { + if (ShaderCache.TryGetValue(textureFormat, out byte[]? cachedCode)) + { + code = cachedCode; + error = null; + return true; + } + + LayerShaderTraits traits = GetTraits(textureFormat); + string source = ShaderTemplate + .Replace("__TEXEL_TYPE__", traits.TexelType, StringComparison.Ordinal) + .Replace("__OUTPUT_FORMAT__", traits.OutputFormat, StringComparison.Ordinal) + .Replace("__DECODE_TEXEL_FUNCTION__", traits.DecodeTexelFunction, StringComparison.Ordinal) + .Replace("__ENCODE_OUTPUT_FUNCTION__", traits.EncodeOutputFunction, StringComparison.Ordinal) + .Replace("__BLEND_AND_COMPOSE__", CompositionShaderSnippets.BlendAndCompose, StringComparison.Ordinal) + .Replace("__LOAD_BACKDROP__", traits.LoadBackdropExpression, StringComparison.Ordinal) + .Replace("__LOAD_SOURCE__", traits.LoadSourceExpression, StringComparison.Ordinal) + .Replace("__STORE_OUTPUT__", traits.StoreOutputStatement, StringComparison.Ordinal); + + byte[] sourceBytes = Encoding.UTF8.GetBytes(source); + code = new byte[sourceBytes.Length + 1]; + sourceBytes.CopyTo(code, 0); + code[^1] = 0; + ShaderCache[textureFormat] = code; + } + + error = null; + return true; + } + + private static LayerShaderTraits GetTraits(TextureFormat textureFormat) + { + return textureFormat switch + { + TextureFormat.R8Unorm => CreateFloatTraits("r8unorm"), + TextureFormat.RG8Unorm => CreateFloatTraits("rg8unorm"), + TextureFormat.Rgba8Unorm => CreateFloatTraits("rgba8unorm"), + TextureFormat.Bgra8Unorm => CreateFloatTraits("bgra8unorm"), + TextureFormat.Rgb10A2Unorm => CreateFloatTraits("rgb10a2unorm"), + TextureFormat.R16float => CreateFloatTraits("r16float"), + TextureFormat.RG16float => CreateFloatTraits("rg16float"), + TextureFormat.Rgba16float => CreateFloatTraits("rgba16float"), + TextureFormat.Rgba32float => CreateFloatTraits("rgba32float"), + TextureFormat.RG8Snorm => CreateSnormTraits("rg8snorm"), + TextureFormat.Rgba8Snorm => CreateSnormTraits("rgba8snorm"), + _ => CreateFloatTraits("rgba8unorm"), + }; + } + + private static LayerShaderTraits CreateFloatTraits(string outputFormat) + { + const string decodeTexel = + """ + fn decode_texel(texel: vec4) -> vec4 { + return texel; + } + """; + + const string encodeOutput = + """ + fn encode_output(color: vec4) -> vec4 { + return color; + } + """; + + return new LayerShaderTraits( + outputFormat, + "f32", + decodeTexel, + encodeOutput, + "textureLoad(backdrop_texture, vec2(dest_x, dest_y), 0)", + "textureLoad(source_texture, vec2(src_x, src_y), 0)", + "textureStore(output_texture, vec2(out_x, out_y), encode_output(vec4(rgb, alpha)));"); + } + + private static LayerShaderTraits CreateSnormTraits(string outputFormat) + { + const string decodeTexel = + """ + fn decode_texel(texel: vec4) -> vec4 { + return (texel * 0.5) + vec4(0.5); + } + """; + + const string encodeOutput = + """ + fn encode_output(color: vec4) -> vec4 { + let clamped = clamp(color, vec4(0.0), vec4(1.0)); + return (clamped * 2.0) - vec4(1.0); + } + """; + + return new LayerShaderTraits( + outputFormat, + "f32", + decodeTexel, + encodeOutput, + "textureLoad(backdrop_texture, vec2(dest_x, dest_y), 0)", + "textureLoad(source_texture, vec2(src_x, src_y), 0)", + "textureStore(output_texture, vec2(out_x, out_y), encode_output(vec4(rgb, alpha)));"); + } + + private readonly struct LayerShaderTraits( + string outputFormat, + string texelType, + string decodeTexelFunction, + string encodeOutputFunction, + string loadBackdropExpression, + string loadSourceExpression, + string storeOutputStatement) + { + public string OutputFormat { get; } = outputFormat; + + public string TexelType { get; } = texelType; + + public string DecodeTexelFunction { get; } = decodeTexelFunction; + + public string EncodeOutputFunction { get; } = encodeOutputFunction; + + public string LoadBackdropExpression { get; } = loadBackdropExpression; + + public string LoadSourceExpression { get; } = loadSourceExpression; + + public string StoreOutputStatement { get; } = storeOutputStatement; + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/CompositeComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/CompositeComputeShader.cs new file mode 100644 index 000000000..93e9d526c --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/CompositeComputeShader.cs @@ -0,0 +1,1402 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Text; +using Silk.NET.WebGPU; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Composites prepared commands over coverage in tile order to produce the final output. +/// Coverage is computed inline using a fixed-point scanline rasterizer ported from +/// , operating per-tile with workgroup shared memory. +/// Shader source is generated per texture format to match sampling/output requirements. +/// +internal static class CompositeComputeShader +{ + private static readonly object CacheSync = new(); + private static readonly Dictionary ShaderCache = []; + + private static readonly string ShaderTemplate = + """ + struct Edge { + x0: i32, + y0: i32, + x1: i32, + y1: i32, + flags: i32, + adj_x: i32, + adj_y: i32, + } + + struct Params { + destination_x: u32, + destination_y: u32, + destination_width: u32, + destination_height: u32, + edge_start: u32, + fill_rule_value: u32, + edge_origin_x: i32, + edge_origin_y: i32, + csr_offsets_start: u32, + csr_band_count: u32, + brush_type: u32, + brush_origin_x: u32, + brush_origin_y: u32, + brush_region_x: u32, + brush_region_y: u32, + brush_region_width: u32, + brush_region_height: u32, + color_blend_mode: u32, + alpha_composition_mode: u32, + blend_percentage: u32, + gp0: u32, + gp1: u32, + gp2: u32, + gp3: u32, + rasterization_mode: u32, + antialias_threshold: u32, + gp4: u32, + gp5: u32, + gp6: u32, + gp7: u32, + stops_offset: u32, + stop_count: u32, + }; + + struct DispatchConfig { + target_width: u32, + target_height: u32, + tile_count_x: u32, + tile_count_y: u32, + tile_count: u32, + command_count: u32, + source_origin_x: u32, + source_origin_y: u32, + output_origin_x: u32, + output_origin_y: u32, + width_in_bins: u32, + height_in_bins: u32, + bin_count: u32, + partition_count: u32, + binning_size: u32, + bin_data_start: u32, + }; + + @group(0) @binding(0) var edges: array; + @group(0) @binding(1) var backdrop_texture: texture_2d<__BACKDROP_TEXEL_TYPE__>; + @group(0) @binding(2) var brush_texture: texture_2d<__BACKDROP_TEXEL_TYPE__>; + @group(0) @binding(3) var output_texture: texture_storage_2d<__OUTPUT_FORMAT__, write>; + @group(0) @binding(4) var commands: array; + @group(0) @binding(5) var dispatch_config: DispatchConfig; + @group(0) @binding(6) var band_offsets: array; + + struct ColorStop { + ratio: f32, + r: f32, + g: f32, + b: f32, + a: f32, + }; + + @group(0) @binding(7) var color_stops: array; + + // Workgroup shared memory for per-tile coverage accumulation. + // Layout: 16 rows x 16 columns. Index = row * 16 + col. + var tile_cover: array, 256>; + var tile_area: array, 256>; + var tile_start_cover: array, 16>; + + const FIXED_SHIFT: u32 = 8u; + const FIXED_ONE: i32 = 256; + const AREA_SHIFT: u32 = 9u; + const COV_STEPS: i32 = 256; + const COV_SCALE: f32 = 1.0 / 256.0; + const EO_MASK: i32 = 511; + const EO_PERIOD: i32 = 512; + + // Brush type constants. Must match PreparedBrushType in WebGPUDrawingBackend.cs. + const BRUSH_SOLID: u32 = 0u; + const BRUSH_IMAGE: u32 = 1u; + const BRUSH_LINEAR_GRADIENT: u32 = 2u; + const BRUSH_RADIAL_GRADIENT: u32 = 3u; + const BRUSH_RADIAL_GRADIENT_TWO_CIRCLE: u32 = 4u; + const BRUSH_ELLIPTIC_GRADIENT: u32 = 5u; + const BRUSH_SWEEP_GRADIENT: u32 = 6u; + const BRUSH_PATTERN: u32 = 7u; + const BRUSH_RECOLOR: u32 = 8u; + + fn u32_to_f32(bits: u32) -> f32 { + return bitcast(bits); + } + + // Exact copy of C# GradientBrushApplicator.this[x, y] color sampling. + // Combines repetition mode + GetGradientSegment + lerp into one function. + // Returns vec4(0) with alpha=0 for DontFill outside [0,1]. + fn sample_brush_gradient(raw_t: f32, mode: u32, offset: u32, count: u32) -> vec4 { + if count == 0u { return vec4(0.0); } + + var t = raw_t; + + // C# switch (this.repetitionMode) + if mode == 1u { + // Repeat: positionOnCompleteGradient %= 1; + t = t % 1.0; + } else if mode == 2u { + // Reflect: positionOnCompleteGradient %= 2; + // if (positionOnCompleteGradient > 1) { positionOnCompleteGradient = 2 - positionOnCompleteGradient; } + t = t % 2.0; + if t > 1.0 { t = 2.0 - t; } + } else if mode == 3u { + // DontFill: if (positionOnCompleteGradient is > 1 or < 0) { return Transparent; } + if t < 0.0 || t > 1.0 { return vec4(0.0); } + } + // mode 0 (None): do nothing + + if count == 1u { + let s = color_stops[offset]; + return vec4(s.r, s.g, s.b, s.a); + } + + // C# GetGradientSegment + // ColorStop localGradientFrom = this.colorStops[0]; + // ColorStop localGradientTo = default; + // foreach (ColorStop colorStop in this.colorStops) + // { + // localGradientTo = colorStop; + // if (colorStop.Ratio > positionOnCompleteGradient) { break; } + // localGradientFrom = localGradientTo; + // } + var from_idx = 0u; + var to_idx = 0u; + for (var i = 0u; i < count; i++) { + to_idx = i; + if color_stops[offset + i].ratio > t { + break; + } + from_idx = i; + } + + let from_stop = color_stops[offset + from_idx]; + let to_stop = color_stops[offset + to_idx]; + + // C#: if (from.Color.Equals(to.Color)) { return from.Color.ToPixel(); } + let from_color = vec4(from_stop.r, from_stop.g, from_stop.b, from_stop.a); + let to_color = vec4(to_stop.r, to_stop.g, to_stop.b, to_stop.a); + if all(from_color == to_color) { + return from_color; + } + + // C#: float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio); + let range = to_stop.ratio - from_stop.ratio; + let local_t = (t - from_stop.ratio) / range; + + // C#: Vector4.Lerp(from.Color.ToScaledVector4(), to.Color.ToScaledVector4(), onLocalGradient) + return mix(from_color, to_color, local_t); + } + + // Linear gradient: project pixel onto gradient axis. + fn linear_gradient_t(x: f32, y: f32, cmd: Params) -> f32 { + let start_x = u32_to_f32(cmd.gp0); + let start_y = u32_to_f32(cmd.gp1); + let end_x = u32_to_f32(cmd.gp2); + let end_y = u32_to_f32(cmd.gp3); + let along_x = end_x - start_x; + let along_y = end_y - start_y; + let along_sq = along_x * along_x + along_y * along_y; + if along_sq < 1e-12 { return 0.0; } + let dx = x - start_x; + let dy = y - start_y; + return (dx * along_x + dy * along_y) / along_sq; + } + + // Single-circle radial gradient. + // gp0=cx, gp1=cy, gp2=radius, gp3=repetition_mode + fn radial_gradient_t(x: f32, y: f32, cmd: Params) -> f32 { + let cx = u32_to_f32(cmd.gp0); + let cy = u32_to_f32(cmd.gp1); + let radius = u32_to_f32(cmd.gp2); + if radius < 1e-20 { return 0.0; } + return length(vec2(x - cx, y - cy)) / radius; + } + + // Two-circle radial gradient. + // gp0=c0.x, gp1=c0.y, gp2=c1.x, gp3=c1.y, gp4=r0, gp5=r1 + fn radial_gradient_two_t(x: f32, y: f32, cmd: Params) -> f32 { + let c0x = u32_to_f32(cmd.gp0); + let c0y = u32_to_f32(cmd.gp1); + let c1x = u32_to_f32(cmd.gp2); + let c1y = u32_to_f32(cmd.gp3); + let r0 = u32_to_f32(cmd.gp4); + let r1 = u32_to_f32(cmd.gp5); + + let dx_c = c1x - c0x; + let dy_c = c1y - c0y; + let dr = r1 - r0; + let dd = dx_c * dx_c + dy_c * dy_c; + let denom = dd - dr * dr; + + let qx = x - c0x; + let qy = y - c0y; + + // Concentric case (centers equal) or degenerate (denom == 0). + if dd < 1e-10 || abs(denom) < 1e-10 { + let dist = length(vec2(qx, qy)); + let abs_dr = max(abs(dr), 1e-20); + return (dist - r0) / abs_dr; + } + + // General case: t = (q·d - r0*dr) / denom. + let num = qx * dx_c + qy * dy_c - r0 * dr; + return num / denom; + } + + // Elliptic gradient. Computes rotation and radii from raw brush properties. + // gp0=center.x, gp1=center.y, gp2=refEnd.x, gp3=refEnd.y, gp4=axisRatio + fn elliptic_gradient_t(x: f32, y: f32, cmd: Params) -> f32 { + let cx = u32_to_f32(cmd.gp0); + let cy = u32_to_f32(cmd.gp1); + let ref_x = u32_to_f32(cmd.gp2); + let ref_y = u32_to_f32(cmd.gp3); + let axis_ratio = u32_to_f32(cmd.gp4); + + let ref_dx = ref_x - cx; + let ref_dy = ref_y - cy; + let rotation = atan2(ref_dy, ref_dx); + let cos_r = cos(rotation); + let sin_r = sin(rotation); + let rx_sq = ref_dx * ref_dx + ref_dy * ref_dy; + let ry_sq = rx_sq * axis_ratio * axis_ratio; + + let px = x - cx; + let py = y - cy; + let rotated_x = px * cos_r - py * sin_r; + let rotated_y = px * sin_r + py * cos_r; + + if rx_sq < 1e-20 { return 0.0; } + if ry_sq < 1e-20 { return 0.0; } + return sqrt(rotated_x * rotated_x / rx_sq + rotated_y * rotated_y / ry_sq); + } + + // Sweep (angular) gradient. Computes radians and sweep from raw degrees. + // gp0=center.x, gp1=center.y, gp2=startAngleDegrees, gp3=endAngleDegrees + fn sweep_gradient_t(x: f32, y: f32, cmd: Params) -> f32 { + let cx = u32_to_f32(cmd.gp0); + let cy = u32_to_f32(cmd.gp1); + let start_deg = u32_to_f32(cmd.gp2); + let end_deg = u32_to_f32(cmd.gp3); + + let start_rad = start_deg * 0.017453292; // PI / 180 + let end_rad = end_deg * 0.017453292; + + // Compute sweep, normalizing to (0, 2PI]. + var sweep = (end_rad - start_rad) % 6.283185307; + if sweep <= 0.0 { sweep += 6.283185307; } + if abs(sweep) < 1e-6 { sweep = 6.283185307; } + let is_full = abs(sweep - 6.283185307) < 1e-6; + let inv_sweep = 1.0 / sweep; + + let dx = x - cx; + let dy = y - cy; + + // atan2(-dy, dx) gives clockwise angles in y-down space. + var angle = atan2(-dy, dx); + if angle < 0.0 { angle += 6.283185307; } + + // Rotate basis by 180 degrees. + angle += 3.141592653; + if angle >= 6.283185307 { angle -= 6.283185307; } + + // Phase measured clockwise from start. + var phase = angle - start_rad; + if phase < 0.0 { phase += 6.283185307; } + + if is_full { + return phase / 6.283185307; + } + return phase * inv_sweep; + } + + __DECODE_TEXEL_FUNCTION__ + + __ENCODE_OUTPUT_FUNCTION__ + + __BLEND_AND_COMPOSE__ + + fn positive_mod(value: i32, divisor: i32) -> i32 { + let m = value % divisor; + return select(m + divisor, m, m >= 0); + } + + // ----------------------------------------------------------------------- + // Fixed-point scanline rasterizer (ported from DefaultRasterizer) + // ----------------------------------------------------------------------- + + fn find_adjustment(value: i32) -> i32 { + let lte0 = (~((value - 1) >> 31)) & 1; + let div256 = (((value & (FIXED_ONE - 1)) - 1) >> 31) & 1; + return lte0 & div256; + } + + fn add_cell(row: i32, col: i32, delta: i32, a: i32) { + if row < 0 || row >= 16 { + return; + } + if col < 0 { + atomicAdd(&tile_start_cover[row], delta); + return; + } + if col >= 16 { + return; + } + let idx = u32(row) * 16u + u32(col); + atomicAdd(&tile_cover[idx], delta); + atomicAdd(&tile_area[idx], a); + } + + fn cell_vertical(px: i32, py: i32, x: i32, y0: i32, y1: i32) { + let delta = y0 - y1; + let a = delta * ((FIXED_ONE * 2) - x - x); + add_cell(py, px, delta, a); + } + + fn cell(row: i32, px: i32, x0: i32, y0: i32, x1: i32, y1: i32) { + let delta = y0 - y1; + let a = delta * ((FIXED_ONE * 2) - x0 - x1); + add_cell(row, px, delta, a); + } + + fn vertical_down(col_index: i32, y0: i32, y1: i32, x: i32) { + let row0 = y0 >> FIXED_SHIFT; + let row1 = (y1 - 1) >> FIXED_SHIFT; + let fy0 = y0 - (row0 << FIXED_SHIFT); + let fy1 = y1 - (row1 << FIXED_SHIFT); + let fx = x - (col_index << FIXED_SHIFT); + if row0 == row1 { + cell_vertical(col_index, row0, fx, fy0, fy1); + return; + } + cell_vertical(col_index, row0, fx, fy0, FIXED_ONE); + for (var row = row0 + 1; row < row1; row++) { + cell_vertical(col_index, row, fx, 0, FIXED_ONE); + } + cell_vertical(col_index, row1, fx, 0, fy1); + } + + fn vertical_up(col_index: i32, y0: i32, y1: i32, x: i32) { + let row0 = (y0 - 1) >> FIXED_SHIFT; + let row1 = y1 >> FIXED_SHIFT; + let fy0 = y0 - (row0 << FIXED_SHIFT); + let fy1 = y1 - (row1 << FIXED_SHIFT); + let fx = x - (col_index << FIXED_SHIFT); + if row0 == row1 { + cell_vertical(col_index, row0, fx, fy0, fy1); + return; + } + cell_vertical(col_index, row0, fx, fy0, 0); + for (var row = row0 - 1; row > row1; row--) { + cell_vertical(col_index, row, fx, FIXED_ONE, 0); + } + cell_vertical(col_index, row1, fx, FIXED_ONE, fy1); + } + + fn row_down_r(row_idx: i32, p0x: i32, p0y: i32, p1x: i32, p1y: i32) { + let col0 = p0x >> FIXED_SHIFT; + let col1 = (p1x - 1) >> FIXED_SHIFT; + let fx0 = p0x - (col0 << FIXED_SHIFT); + let fx1 = p1x - (col1 << FIXED_SHIFT); + if col0 == col1 { + cell(row_idx, col0, fx0, p0y, fx1, p1y); + return; + } + let dx = p1x - p0x; + let dy = p1y - p0y; + let pp = (FIXED_ONE - fx0) * dy; + var cy = p0y + (pp / dx); + cell(row_idx, col0, fx0, p0y, FIXED_ONE, cy); + var idx = col0 + 1; + if idx != col1 { + var md = (pp % dx) - dx; + let p = FIXED_ONE * dy; + let lift = p / dx; + let rem = p % dx; + for (; idx != col1; idx++) { + var delta = lift; + md += rem; + if md >= 0 { + md -= dx; + delta++; + } + let ny = cy + delta; + cell(row_idx, idx, 0, cy, FIXED_ONE, ny); + cy = ny; + } + } + cell(row_idx, col1, 0, cy, fx1, p1y); + } + + fn row_down_r_v(row_idx: i32, p0x: i32, p0y: i32, p1x: i32, p1y: i32) { + if p0x < p1x { + row_down_r(row_idx, p0x, p0y, p1x, p1y); + } else { + let ci = (p0x - find_adjustment(p0x)) >> FIXED_SHIFT; + let x = p0x - (ci << FIXED_SHIFT); + cell_vertical(ci, row_idx, x, p0y, p1y); + } + } + + fn row_up_r(row_idx: i32, p0x: i32, p0y: i32, p1x: i32, p1y: i32) { + let col0 = p0x >> FIXED_SHIFT; + let col1 = (p1x - 1) >> FIXED_SHIFT; + let fx0 = p0x - (col0 << FIXED_SHIFT); + let fx1 = p1x - (col1 << FIXED_SHIFT); + if col0 == col1 { + cell(row_idx, col0, fx0, p0y, fx1, p1y); + return; + } + let dx = p1x - p0x; + let dy = p0y - p1y; + let pp = (FIXED_ONE - fx0) * dy; + var cy = p0y - (pp / dx); + cell(row_idx, col0, fx0, p0y, FIXED_ONE, cy); + var idx = col0 + 1; + if idx != col1 { + var md = (pp % dx) - dx; + let p = FIXED_ONE * dy; + let lift = p / dx; + let rem = p % dx; + for (; idx != col1; idx++) { + var delta = lift; + md += rem; + if md >= 0 { + md -= dx; + delta++; + } + let ny = cy - delta; + cell(row_idx, idx, 0, cy, FIXED_ONE, ny); + cy = ny; + } + } + cell(row_idx, col1, 0, cy, fx1, p1y); + } + + fn row_up_r_v(row_idx: i32, p0x: i32, p0y: i32, p1x: i32, p1y: i32) { + if p0x < p1x { + row_up_r(row_idx, p0x, p0y, p1x, p1y); + } else { + let ci = (p0x - find_adjustment(p0x)) >> FIXED_SHIFT; + let x = p0x - (ci << FIXED_SHIFT); + cell_vertical(ci, row_idx, x, p0y, p1y); + } + } + + fn row_down_l(row_idx: i32, p0x: i32, p0y: i32, p1x: i32, p1y: i32) { + let col0 = (p0x - 1) >> FIXED_SHIFT; + let col1 = p1x >> FIXED_SHIFT; + let fx0 = p0x - (col0 << FIXED_SHIFT); + let fx1 = p1x - (col1 << FIXED_SHIFT); + if col0 == col1 { + cell(row_idx, col0, fx0, p0y, fx1, p1y); + return; + } + let dx = p0x - p1x; + let dy = p1y - p0y; + let pp = fx0 * dy; + var cy = p0y + (pp / dx); + cell(row_idx, col0, fx0, p0y, 0, cy); + var idx = col0 - 1; + if idx != col1 { + var md = (pp % dx) - dx; + let p = FIXED_ONE * dy; + let lift = p / dx; + let rem = p % dx; + for (; idx != col1; idx--) { + var delta = lift; + md += rem; + if md >= 0 { + md -= dx; + delta++; + } + let ny = cy + delta; + cell(row_idx, idx, FIXED_ONE, cy, 0, ny); + cy = ny; + } + } + cell(row_idx, col1, FIXED_ONE, cy, fx1, p1y); + } + + fn row_down_l_v(row_idx: i32, p0x: i32, p0y: i32, p1x: i32, p1y: i32) { + if p0x > p1x { + row_down_l(row_idx, p0x, p0y, p1x, p1y); + } else { + let ci = (p0x - find_adjustment(p0x)) >> FIXED_SHIFT; + let x = p0x - (ci << FIXED_SHIFT); + cell_vertical(ci, row_idx, x, p0y, p1y); + } + } + + fn row_up_l(row_idx: i32, p0x: i32, p0y: i32, p1x: i32, p1y: i32) { + let col0 = (p0x - 1) >> FIXED_SHIFT; + let col1 = p1x >> FIXED_SHIFT; + let fx0 = p0x - (col0 << FIXED_SHIFT); + let fx1 = p1x - (col1 << FIXED_SHIFT); + if col0 == col1 { + cell(row_idx, col0, fx0, p0y, fx1, p1y); + return; + } + let dx = p0x - p1x; + let dy = p0y - p1y; + let pp = fx0 * dy; + var cy = p0y - (pp / dx); + cell(row_idx, col0, fx0, p0y, 0, cy); + var idx = col0 - 1; + if idx != col1 { + var md = (pp % dx) - dx; + let p = FIXED_ONE * dy; + let lift = p / dx; + let rem = p % dx; + for (; idx != col1; idx--) { + var delta = lift; + md += rem; + if md >= 0 { + md -= dx; + delta++; + } + let ny = cy - delta; + cell(row_idx, idx, FIXED_ONE, cy, 0, ny); + cy = ny; + } + } + cell(row_idx, col1, FIXED_ONE, cy, fx1, p1y); + } + + fn row_up_l_v(row_idx: i32, p0x: i32, p0y: i32, p1x: i32, p1y: i32) { + if p0x > p1x { + row_up_l(row_idx, p0x, p0y, p1x, p1y); + } else { + let ci = (p0x - find_adjustment(p0x)) >> FIXED_SHIFT; + let x = p0x - (ci << FIXED_SHIFT); + cell_vertical(ci, row_idx, x, p0y, p1y); + } + } + + fn line_down_r(row0: i32, row1: i32, x0: i32, y0: i32, x1: i32, y1: i32) { + let dx = x1 - x0; + let dy = y1 - y0; + let fy0 = y0 - (row0 << FIXED_SHIFT); + let fy1 = y1 - (row1 << FIXED_SHIFT); + let p_init = (FIXED_ONE - fy0) * dx; + let delta_init = p_init / dy; + var cx = x0 + delta_init; + row_down_r_v(row0, x0, fy0, cx, FIXED_ONE); + var row = row0 + 1; + if row != row1 { + var md = (p_init % dy) - dy; + let p = FIXED_ONE * dx; + let lift = p / dy; + let rem = p % dy; + for (; row != row1; row++) { + var delta = lift; + md += rem; + if md >= 0 { + md -= dy; + delta++; + } + let nx = cx + delta; + row_down_r_v(row, cx, 0, nx, FIXED_ONE); + cx = nx; + } + } + row_down_r_v(row1, cx, 0, x1, fy1); + } + + fn line_up_r(row0: i32, row1: i32, x0: i32, y0: i32, x1: i32, y1: i32) { + let dx = x1 - x0; + let dy = y0 - y1; + let fy0 = y0 - (row0 << FIXED_SHIFT); + let fy1 = y1 - (row1 << FIXED_SHIFT); + let p_init = fy0 * dx; + let delta_init = p_init / dy; + var cx = x0 + delta_init; + row_up_r_v(row0, x0, fy0, cx, 0); + var row = row0 - 1; + if row != row1 { + var md = (p_init % dy) - dy; + let p = FIXED_ONE * dx; + let lift = p / dy; + let rem = p % dy; + for (; row != row1; row--) { + var delta = lift; + md += rem; + if md >= 0 { + md -= dy; + delta++; + } + let nx = cx + delta; + row_up_r_v(row, cx, FIXED_ONE, nx, 0); + cx = nx; + } + } + row_up_r_v(row1, cx, FIXED_ONE, x1, fy1); + } + + fn line_down_l(row0: i32, row1: i32, x0: i32, y0: i32, x1: i32, y1: i32) { + let dx = x0 - x1; + let dy = y1 - y0; + let fy0 = y0 - (row0 << FIXED_SHIFT); + let fy1 = y1 - (row1 << FIXED_SHIFT); + let p_init = (FIXED_ONE - fy0) * dx; + let delta_init = p_init / dy; + var cx = x0 - delta_init; + row_down_l_v(row0, x0, fy0, cx, FIXED_ONE); + var row = row0 + 1; + if row != row1 { + var md = (p_init % dy) - dy; + let p = FIXED_ONE * dx; + let lift = p / dy; + let rem = p % dy; + for (; row != row1; row++) { + var delta = lift; + md += rem; + if md >= 0 { + md -= dy; + delta++; + } + let nx = cx - delta; + row_down_l_v(row, cx, 0, nx, FIXED_ONE); + cx = nx; + } + } + row_down_l_v(row1, cx, 0, x1, fy1); + } + + fn line_up_l(row0: i32, row1: i32, x0: i32, y0: i32, x1: i32, y1: i32) { + let dx = x0 - x1; + let dy = y0 - y1; + let fy0 = y0 - (row0 << FIXED_SHIFT); + let fy1 = y1 - (row1 << FIXED_SHIFT); + let p_init = fy0 * dx; + let delta_init = p_init / dy; + var cx = x0 - delta_init; + row_up_l_v(row0, x0, fy0, cx, 0); + var row = row0 - 1; + if row != row1 { + var md = (p_init % dy) - dy; + let p = FIXED_ONE * dx; + let lift = p / dy; + let rem = p % dy; + for (; row != row1; row--) { + var delta = lift; + md += rem; + if md >= 0 { + md -= dy; + delta++; + } + let nx = cx - delta; + row_up_l_v(row, cx, FIXED_ONE, nx, 0); + cx = nx; + } + } + row_up_l_v(row1, cx, FIXED_ONE, x1, fy1); + } + + fn rasterize_line(x0: i32, y0: i32, x1: i32, y1: i32) { + if x0 == x1 { + let ci = (x0 - find_adjustment(x0)) >> FIXED_SHIFT; + if y0 < y1 { + vertical_down(ci, y0, y1, x0); + } else { + vertical_up(ci, y0, y1, x0); + } + return; + } + if y0 < y1 { + let r0 = y0 >> FIXED_SHIFT; + let r1 = (y1 - 1) >> FIXED_SHIFT; + if r0 == r1 { + let base_y = r0 << FIXED_SHIFT; + if x0 < x1 { + row_down_r(r0, x0, y0 - base_y, x1, y1 - base_y); + } else { + row_down_l(r0, x0, y0 - base_y, x1, y1 - base_y); + } + } else if x0 < x1 { + line_down_r(r0, r1, x0, y0, x1, y1); + } else { + line_down_l(r0, r1, x0, y0, x1, y1); + } + return; + } + let r0 = (y0 - 1) >> FIXED_SHIFT; + let r1 = y1 >> FIXED_SHIFT; + if r0 == r1 { + let base_y = r0 << FIXED_SHIFT; + if x0 < x1 { + row_up_r(r0, x0, y0 - base_y, x1, y1 - base_y); + } else { + row_up_l(r0, x0, y0 - base_y, x1, y1 - base_y); + } + } else if x0 < x1 { + line_up_r(r0, r1, x0, y0, x1, y1); + } else { + line_up_l(r0, r1, x0, y0, x1, y1); + } + } + + fn clip_test(p: f32, q: f32, t0_in: f32, t1_in: f32) -> vec3 { + // Returns (t0, t1, valid) where valid > 0 means the segment is not rejected. + if p == 0.0 { + if q >= 0.0 { + return vec3(t0_in, t1_in, 1.0); + } + return vec3(t0_in, t1_in, -1.0); + } + let r = q / p; + if p < 0.0 { + if r > t1_in { + return vec3(t0_in, t1_in, -1.0); + } + return vec3(max(t0_in, r), t1_in, 1.0); + } + // p > 0 + if r < t0_in { + return vec3(t0_in, t1_in, -1.0); + } + return vec3(t0_in, min(t1_in, r), 1.0); + } + + struct ClippedEdge { + x0: i32, + y0: i32, + x1: i32, + y1: i32, + valid: i32, + } + + fn clip_vertical(ex0: i32, ey0: i32, ex1: i32, ey1: i32, min_y: i32, max_y: i32) -> ClippedEdge { + var t0 = 0.0; + var t1 = 1.0; + let ox = f32(ex0); + let oy = f32(ey0); + let dx = f32(ex1 - ex0); + let dy = f32(ey1 - ey0); + let res1 = clip_test(-dy, oy - f32(min_y), t0, t1); + if res1.z < 0.0 { + return ClippedEdge(ex0, ey0, ex1, ey1, 0); + } + t0 = res1.x; + t1 = res1.y; + let res2 = clip_test(dy, f32(max_y) - oy, t0, t1); + if res2.z < 0.0 { + return ClippedEdge(ex0, ey0, ex1, ey1, 0); + } + t0 = res2.x; + t1 = res2.y; + var rx0 = ex0; + var ry0 = ey0; + var rx1 = ex1; + var ry1 = ey1; + if t1 < 1.0 { + rx1 = i32(round(ox + dx * t1)); + ry1 = i32(round(oy + dy * t1)); + } + if t0 > 0.0 { + rx0 = i32(round(ox + dx * t0)); + ry0 = i32(round(oy + dy * t0)); + } + if ry0 == ry1 { + return ClippedEdge(rx0, ry0, rx1, ry1, 0); + } + return ClippedEdge(rx0, ry0, rx1, ry1, 1); + } + + fn accumulate_start_cover(ey0: i32, ey1: i32, clip_top: i32, clip_bottom: i32, tile_top_fixed: i32) { + // Fast path for edges entirely left of the tile. + // Only start_cover is affected (no area). The total cover delta per row + // is the signed height of the edge within that row, which telescopes + // across columns. This avoids the full column-walking overhead. + var cy0 = clamp(ey0, clip_top, clip_bottom); + var cy1 = clamp(ey1, clip_top, clip_bottom); + if cy0 == cy1 { return; } + + let ly0 = cy0 - tile_top_fixed; + let ly1 = cy1 - tile_top_fixed; + + if ly0 < ly1 { + // Downward. + let row0 = ly0 >> FIXED_SHIFT; + let row1 = (ly1 - 1) >> FIXED_SHIFT; + let fy0 = ly0 - (row0 << FIXED_SHIFT); + let fy1 = ly1 - (row1 << FIXED_SHIFT); + if row0 == row1 { + atomicAdd(&tile_start_cover[row0], fy0 - fy1); + return; + } + atomicAdd(&tile_start_cover[row0], fy0 - FIXED_ONE); + for (var r = row0 + 1; r < row1; r++) { + atomicAdd(&tile_start_cover[r], -FIXED_ONE); + } + atomicAdd(&tile_start_cover[row1], -fy1); + } else { + // Upward. + let row0 = (ly0 - 1) >> FIXED_SHIFT; + let row1 = ly1 >> FIXED_SHIFT; + let fy0 = ly0 - (row0 << FIXED_SHIFT); + let fy1 = ly1 - (row1 << FIXED_SHIFT); + if row0 == row1 { + atomicAdd(&tile_start_cover[row0], fy0 - fy1); + return; + } + atomicAdd(&tile_start_cover[row0], fy0); + for (var r = row0 - 1; r > row1; r--) { + atomicAdd(&tile_start_cover[r], FIXED_ONE); + } + atomicAdd(&tile_start_cover[row1], FIXED_ONE - fy1); + } + } + + fn rasterize_edge(edge: Edge, band_top: i32, band_left_fixed: i32, clip_top_fixed: i32, clip_bottom_fixed: i32) { + let band_top_fixed = band_top << FIXED_SHIFT; + let ex0 = edge.x0 - band_left_fixed; + let ey0 = edge.y0; + let ex1 = edge.x1 - band_left_fixed; + let ey1 = edge.y1; + if ey0 >= clip_top_fixed && ey0 < clip_bottom_fixed && ey1 >= clip_top_fixed && ey1 < clip_bottom_fixed { + rasterize_line(ex0, ey0 - band_top_fixed, ex1, ey1 - band_top_fixed); + return; + } + let clipped = clip_vertical(ex0, ey0, ex1, ey1, clip_top_fixed, clip_bottom_fixed); + if clipped.valid == 0 { + return; + } + rasterize_line(clipped.x0, clipped.y0 - band_top_fixed, clipped.x1, clipped.y1 - band_top_fixed); + } + + fn area_to_coverage(area_val: i32, fill_rule: u32, rasterization_mode: u32, antialias_threshold: f32) -> f32 { + let signed_area = area_val >> AREA_SHIFT; + var abs_area: i32; + if signed_area < 0 { + abs_area = -signed_area; + } else { + abs_area = signed_area; + } + var coverage: f32; + if fill_rule == 0u { + // Non-zero winding + if abs_area >= COV_STEPS { + coverage = 1.0; + } else { + coverage = f32(abs_area) * COV_SCALE; + } + } else { + // Even-odd + var wrapped = abs_area & EO_MASK; + if wrapped > COV_STEPS { + wrapped = EO_PERIOD - wrapped; + } + if wrapped >= COV_STEPS { + coverage = 1.0; + } else { + coverage = f32(wrapped) * COV_SCALE; + } + } + // Aliased mode: snap to binary coverage using threshold + if rasterization_mode == 1u { + if coverage >= antialias_threshold { + coverage = 1.0; + } else { + coverage = 0.0; + } + } + return coverage; + } + + // ----------------------------------------------------------------------- + // Main entry point + // ----------------------------------------------------------------------- + + @compute @workgroup_size(16, 16, 1) + fn cs_main( + @builtin(local_invocation_id) local_id: vec3, + @builtin(workgroup_id) wg_id: vec3 + ) { + let tile_x = wg_id.x; + let tile_y = wg_id.y; + let tile_index = tile_y * dispatch_config.tile_count_x + tile_x; + if tile_index >= dispatch_config.tile_count { + return; + } + + let px = local_id.x; + let py = local_id.y; + let thread_id = py * 16u + px; + + let dest_x = tile_x * 16u + px; + let dest_y = tile_y * 16u + py; + + let in_bounds = dest_x < dispatch_config.target_width && dest_y < dispatch_config.target_height; + + let source_x = i32(dest_x + dispatch_config.source_origin_x); + let source_y = i32(dest_y + dispatch_config.source_origin_y); + let output_x_i32 = i32(dest_x + dispatch_config.output_origin_x); + let output_y_i32 = i32(dest_y + dispatch_config.output_origin_y); + + var destination: vec4; + if in_bounds { + let source = __LOAD_BACKDROP__; + destination = vec4(source.rgb * source.a, source.a); + } + + let dest_x_i32 = i32(dest_x); + let dest_y_i32 = i32(dest_y); + let tile_min_x = i32(tile_x * 16u); + let tile_min_y = i32(tile_y * 16u); + let tile_max_x = tile_min_x + 16; + let tile_max_y = tile_min_y + 16; + + for (var command_index = 0u; command_index < dispatch_config.command_count; command_index++) { + let command = commands[command_index]; + + // Tile vs command bounding box check (uniform across workgroup). + let cmd_min_x = bitcast(command.destination_x); + let cmd_min_y = bitcast(command.destination_y); + let cmd_max_x = cmd_min_x + i32(command.destination_width); + let cmd_max_y = cmd_min_y + i32(command.destination_height); + if tile_max_x <= cmd_min_x || tile_min_x >= cmd_max_x || tile_max_y <= cmd_min_y || tile_min_y >= cmd_max_y { + continue; + } + + var coverage_value = 0.0; + + // Tile position in edge-local (coverage-local) space. + let band_top = tile_min_y - command.edge_origin_y; + let band_left_fixed = (tile_min_x - command.edge_origin_x) << FIXED_SHIFT; + + // Multi-band lookup: tile may overlap one or two bands. + var first_band = band_top / 16; + if band_top < 0 && (band_top % 16) != 0 { + first_band -= 1; + } + first_band = max(first_band, 0); + let last_band = min((band_top + 15) / 16, i32(command.csr_band_count) - 1); + + if first_band > last_band { + continue; + } + + let edge_range_start = band_offsets[command.csr_offsets_start + u32(first_band)]; + let edge_range_end = band_offsets[command.csr_offsets_start + u32(last_band) + 1u]; + if edge_range_start == edge_range_end { + continue; + } + + // Clear shared coverage memory. + atomicStore(&tile_cover[thread_id], 0); + atomicStore(&tile_area[thread_id], 0); + if px == 0u { + atomicStore(&tile_start_cover[py], 0); + } + workgroupBarrier(); + + // Cooperatively rasterize edges from each overlapping band. + let tile_top_fixed = band_top << FIXED_SHIFT; + let tile_bottom_fixed = tile_top_fixed + (i32(16) << FIXED_SHIFT); + let tile_right_fixed = band_left_fixed + (i32(16) << FIXED_SHIFT); + + for (var band = first_band; band <= last_band; band++) { + let b_start = band_offsets[command.csr_offsets_start + u32(band)]; + let b_end = band_offsets[command.csr_offsets_start + u32(band) + 1u]; + let b_count = b_end - b_start; + + let csr_band_top_fixed = band * (i32(16) << FIXED_SHIFT); + let csr_band_bottom_fixed = csr_band_top_fixed + (i32(16) << FIXED_SHIFT); + let clip_top = max(tile_top_fixed, csr_band_top_fixed); + let clip_bottom = min(tile_bottom_fixed, csr_band_bottom_fixed); + + var ei = thread_id; + loop { + if ei >= b_count { + break; + } + let edge = edges[command.edge_start + b_start + ei]; + if edge.y0 == edge.y1 { + // Skip degenerate edges (sentinel slots from stroke expand). + } else if min(edge.x0, edge.x1) >= tile_right_fixed { + } else if max(edge.x0, edge.x1) < band_left_fixed { + accumulate_start_cover(edge.y0, edge.y1, clip_top, clip_bottom, tile_top_fixed); + } else { + rasterize_edge(edge, band_top, band_left_fixed, clip_top, clip_bottom); + } + ei += 256u; + } + } + workgroupBarrier(); + + // Compute coverage. + if in_bounds { + if dest_x_i32 >= cmd_min_x && dest_x_i32 < cmd_max_x && dest_y_i32 >= cmd_min_y && dest_y_i32 < cmd_max_y { + var cover = atomicLoad(&tile_start_cover[py]); + for (var col = 0u; col < px; col++) { + cover += atomicLoad(&tile_cover[py * 16u + col]); + } + let area_val = atomicLoad(&tile_area[py * 16u + px]) + (cover << AREA_SHIFT); + coverage_value = area_to_coverage(area_val, command.fill_rule_value, command.rasterization_mode, u32_to_f32(command.antialias_threshold)); + } + } + + // Compose coverage result (shared by fill and stroke paths). + if in_bounds && coverage_value > 0.0 { + if dest_x_i32 >= cmd_min_x && dest_x_i32 < cmd_max_x && dest_y_i32 >= cmd_min_y && dest_y_i32 < cmd_max_y { + let blend_percentage = u32_to_f32(command.blend_percentage); + let effective_coverage = coverage_value * blend_percentage; + + var brush = vec4( + u32_to_f32(command.gp0), + u32_to_f32(command.gp1), + u32_to_f32(command.gp2), + u32_to_f32(command.gp3)); + + if command.brush_type == BRUSH_IMAGE { + let origin_x = bitcast(command.brush_origin_x); + let origin_y = bitcast(command.brush_origin_y); + let region_x = i32(command.brush_region_x); + let region_y = i32(command.brush_region_y); + let region_w = i32(command.brush_region_width); + let region_h = i32(command.brush_region_height); + let sample_x = positive_mod(dest_x_i32 - origin_x, region_w) + region_x; + let sample_y = positive_mod(dest_y_i32 - origin_y, region_h) + region_y; + brush = __LOAD_BRUSH__; + } else if command.brush_type == BRUSH_LINEAR_GRADIENT { + let px = f32(source_x) + 0.5; + let py = f32(source_y) + 0.5; + let raw_t = linear_gradient_t(px, py, command); + brush = sample_brush_gradient(raw_t, command.gp4, command.stops_offset, command.stop_count); + } else if command.brush_type == BRUSH_RADIAL_GRADIENT { + let px = f32(source_x) + 0.5; + let py = f32(source_y) + 0.5; + let raw_t = radial_gradient_t(px, py, command); + brush = sample_brush_gradient(raw_t, command.gp4, command.stops_offset, command.stop_count); + } else if command.brush_type == BRUSH_RADIAL_GRADIENT_TWO_CIRCLE { + let px = f32(source_x) + 0.5; + let py = f32(source_y) + 0.5; + let raw_t = radial_gradient_two_t(px, py, command); + brush = sample_brush_gradient(raw_t, command.gp6, command.stops_offset, command.stop_count); + } else if command.brush_type == BRUSH_ELLIPTIC_GRADIENT { + let px = f32(source_x) + 0.5; + let py = f32(source_y) + 0.5; + let raw_t = elliptic_gradient_t(px, py, command); + brush = sample_brush_gradient(raw_t, command.gp5, command.stops_offset, command.stop_count); + } else if command.brush_type == BRUSH_SWEEP_GRADIENT { + let px = f32(source_x) + 0.5; + let py = f32(source_y) + 0.5; + let raw_t = sweep_gradient_t(px, py, command); + brush = sample_brush_gradient(raw_t, command.gp4, command.stops_offset, command.stop_count); + } else if command.brush_type == BRUSH_PATTERN { + let pw = u32_to_f32(command.gp0); + let ph = u32_to_f32(command.gp1); + let ox = u32_to_f32(command.gp2); + let oy = u32_to_f32(command.gp3); + let fx = f32(source_x) - ox; + let fy = f32(source_y) - oy; + let pw_i = i32(pw); + let ph_i = i32(ph); + let pxi = ((i32(fx) % pw_i) + pw_i) % pw_i; + let pyi = ((i32(fy) % ph_i) + ph_i) % ph_i; + let idx = command.stops_offset + u32(pyi) * u32(pw_i) + u32(pxi); + let c = color_stops[idx]; + brush = vec4(c.r, c.g, c.b, c.a); + } else if command.brush_type == BRUSH_RECOLOR { + let src_r = u32_to_f32(command.gp0); + let src_g = u32_to_f32(command.gp1); + let src_b = u32_to_f32(command.gp2); + let src_a = u32_to_f32(command.gp3); + let tgt_r = u32_to_f32(command.gp4); + let tgt_g = u32_to_f32(command.gp5); + let tgt_b = u32_to_f32(command.gp6); + let tgt_a = u32_to_f32(command.gp7); + let threshold = bitcast(command.stops_offset); + let dr = destination.r - src_r; + let dg = destination.g - src_g; + let db = destination.b - src_b; + let da = destination.a - src_a; + let dist_sq = dr * dr + dg * dg + db * db + da * da; + if dist_sq <= threshold * threshold { + brush = vec4(tgt_r, tgt_g, tgt_b, tgt_a); + } else { + brush = destination; + } + } + + let src = vec4(brush.rgb, brush.a * effective_coverage); + destination = compose_pixel(destination, src, command.color_blend_mode, command.alpha_composition_mode); + } + } + workgroupBarrier(); + } + + if in_bounds { + let alpha = destination.a; + let rgb = unpremultiply(destination.rgb, alpha); + __STORE_OUTPUT__ + } + } + """; + + /// + /// Gets the input sample type required for the fine composite shader variant. + /// + public static bool TryGetInputSampleType(TextureFormat textureFormat, out TextureSampleType sampleType) + { + if (TryGetTraits(textureFormat, out ShaderTraits traits)) + { + sampleType = traits.SampleType; + return true; + } + + sampleType = default; + return false; + } + + /// + /// Gets the null-terminated WGSL source for the fine composite shader variant. + /// + public static bool TryGetCode(TextureFormat textureFormat, out byte[] code, out string? error) + { + if (!TryGetTraits(textureFormat, out ShaderTraits traits)) + { + code = []; + error = $"Prepared composite fine shader does not support texture format '{textureFormat}'."; + return false; + } + + lock (CacheSync) + { + if (ShaderCache.TryGetValue(textureFormat, out byte[]? cachedCode)) + { + code = cachedCode; + error = null; + return true; + } + + string source = ShaderTemplate + .Replace("__BACKDROP_TEXEL_TYPE__", traits.BackdropTexelType, StringComparison.Ordinal) + .Replace("__OUTPUT_FORMAT__", traits.OutputFormat, StringComparison.Ordinal) + .Replace("__DECODE_TEXEL_FUNCTION__", traits.DecodeTexelFunction, StringComparison.Ordinal) + .Replace("__ENCODE_OUTPUT_FUNCTION__", traits.EncodeOutputFunction, StringComparison.Ordinal) + .Replace("__BLEND_AND_COMPOSE__", CompositionShaderSnippets.BlendAndCompose, StringComparison.Ordinal) + .Replace("__LOAD_BACKDROP__", traits.LoadBackdropExpression, StringComparison.Ordinal) + .Replace("__LOAD_BRUSH__", traits.LoadBrushExpression, StringComparison.Ordinal) + .Replace("__STORE_OUTPUT__", traits.StoreOutputStatement, StringComparison.Ordinal); + + byte[] sourceBytes = Encoding.UTF8.GetBytes(source); + code = new byte[sourceBytes.Length + 1]; + sourceBytes.CopyTo(code, 0); + code[^1] = 0; + ShaderCache[textureFormat] = code; + } + + error = null; + return true; + } + + /// + /// Resolves shader traits for the provided texture format. + /// + private static bool TryGetTraits(TextureFormat textureFormat, out ShaderTraits traits) + { + switch (textureFormat) + { + case TextureFormat.R8Unorm: + traits = CreateFloatTraits("r8unorm"); + return true; + case TextureFormat.RG8Unorm: + traits = CreateFloatTraits("rg8unorm"); + return true; + case TextureFormat.Rgba8Unorm: + traits = CreateFloatTraits("rgba8unorm"); + return true; + case TextureFormat.Bgra8Unorm: + traits = CreateFloatTraits("bgra8unorm"); + return true; + case TextureFormat.Rgb10A2Unorm: + traits = CreateFloatTraits("rgb10a2unorm"); + return true; + case TextureFormat.R16float: + traits = CreateFloatTraits("r16float"); + return true; + case TextureFormat.RG16float: + traits = CreateFloatTraits("rg16float"); + return true; + case TextureFormat.Rgba16float: + traits = CreateFloatTraits("rgba16float"); + return true; + case TextureFormat.Rgba32float: + traits = CreateFloatTraits("rgba32float"); + return true; + case TextureFormat.RG8Snorm: + traits = CreateSnormTraits("rg8snorm"); + return true; + case TextureFormat.Rgba8Snorm: + traits = CreateSnormTraits("rgba8snorm"); + return true; + case TextureFormat.Rgba8Uint: + traits = CreateUintTraits("rgba8uint", 255F); + return true; + case TextureFormat.R16Uint: + traits = CreateUintTraits("r16uint", 65535F); + return true; + case TextureFormat.RG16Uint: + traits = CreateUintTraits("rg16uint", 65535F); + return true; + case TextureFormat.Rgba16Uint: + traits = CreateUintTraits("rgba16uint", 65535F); + return true; + case TextureFormat.RG16Sint: + traits = CreateSintTraits("rg16sint", -32768F, 32767F); + return true; + case TextureFormat.Rgba16Sint: + traits = CreateSintTraits("rgba16sint", -32768F, 32767F); + return true; + default: + traits = default; + return false; + } + } + + private static ShaderTraits CreateFloatTraits(string outputFormat) + { + const string decodeTexel = + """ + fn decode_texel(texel: vec4) -> vec4 { + return texel; + } + """; + + const string encodeOutput = + """ + fn encode_output(color: vec4) -> vec4 { + return color; + } + """; + + return new ShaderTraits( + outputFormat, + "f32", + TextureSampleType.Float, + decodeTexel, + encodeOutput, + "decode_texel(textureLoad(backdrop_texture, vec2(source_x, source_y), 0))", + "decode_texel(textureLoad(brush_texture, vec2(sample_x, sample_y), 0))", + "textureStore(output_texture, vec2(output_x_i32, output_y_i32), encode_output(vec4(rgb, alpha)));"); + } + + private static ShaderTraits CreateSnormTraits(string outputFormat) + { + const string decodeTexel = + """ + fn decode_texel(texel: vec4) -> vec4 { + return (texel * 0.5) + vec4(0.5); + } + """; + + const string encodeOutput = + """ + fn encode_output(color: vec4) -> vec4 { + let clamped = clamp(color, vec4(0.0), vec4(1.0)); + return (clamped * 2.0) - vec4(1.0); + } + """; + + return new ShaderTraits( + outputFormat, + "f32", + TextureSampleType.Float, + decodeTexel, + encodeOutput, + "decode_texel(textureLoad(backdrop_texture, vec2(source_x, source_y), 0))", + "decode_texel(textureLoad(brush_texture, vec2(sample_x, sample_y), 0))", + "textureStore(output_texture, vec2(output_x_i32, output_y_i32), encode_output(vec4(rgb, alpha)));"); + } + + private static ShaderTraits CreateUintTraits(string outputFormat, float maxValue) + { + string maxVector = $"vec4({maxValue:F1}, {maxValue:F1}, {maxValue:F1}, {maxValue:F1})"; + string decodeTexel = $@"const UINT_TEXEL_MAX: vec4 = {maxVector}; +fn decode_texel(texel: vec4) -> vec4 {{ + return vec4(texel) / UINT_TEXEL_MAX; +}}"; + const string encodeOutput = + """ + fn encode_output(color: vec4) -> vec4 { + let clamped = clamp(color, vec4(0.0), vec4(1.0)); + return vec4(round(clamped * UINT_TEXEL_MAX)); + } + """; + + return new ShaderTraits( + outputFormat, + "u32", + TextureSampleType.Uint, + decodeTexel, + encodeOutput, + "decode_texel(textureLoad(backdrop_texture, vec2(source_x, source_y), 0))", + "decode_texel(textureLoad(brush_texture, vec2(sample_x, sample_y), 0))", + "textureStore(output_texture, vec2(output_x_i32, output_y_i32), encode_output(vec4(rgb, alpha)));"); + } + + private static ShaderTraits CreateSintTraits(string outputFormat, float minValue, float maxValue) + { + string minVector = $"vec4({minValue:F1}, {minValue:F1}, {minValue:F1}, {minValue:F1})"; + string maxVector = $"vec4({maxValue:F1}, {maxValue:F1}, {maxValue:F1}, {maxValue:F1})"; + string decodeTexel = $@"const SINT_TEXEL_MIN: vec4 = {minVector}; +const SINT_TEXEL_MAX: vec4 = {maxVector}; +const SINT_TEXEL_RANGE: vec4 = SINT_TEXEL_MAX - SINT_TEXEL_MIN; +fn decode_texel(texel: vec4) -> vec4 {{ + return (vec4(texel) - SINT_TEXEL_MIN) / SINT_TEXEL_RANGE; +}}"; + const string encodeOutput = + """ + fn encode_output(color: vec4) -> vec4 { + let clamped = clamp(color, vec4(0.0), vec4(1.0)); + return vec4(round((clamped * SINT_TEXEL_RANGE) + SINT_TEXEL_MIN)); + } + """; + + return new ShaderTraits( + outputFormat, + "i32", + TextureSampleType.Sint, + decodeTexel, + encodeOutput, + "decode_texel(textureLoad(backdrop_texture, vec2(source_x, source_y), 0))", + "decode_texel(textureLoad(brush_texture, vec2(sample_x, sample_y), 0))", + "textureStore(output_texture, vec2(output_x_i32, output_y_i32), encode_output(vec4(rgb, alpha)));"); + } + + private readonly struct ShaderTraits( + string outputFormat, + string backdropTexelType, + TextureSampleType sampleType, + string decodeTexelFunction, + string encodeOutputFunction, + string loadBackdropExpression, + string loadBrushExpression, + string storeOutputStatement) + { + public string OutputFormat { get; } = outputFormat; + + public string BackdropTexelType { get; } = backdropTexelType; + + public TextureSampleType SampleType { get; } = sampleType; + + public string DecodeTexelFunction { get; } = decodeTexelFunction; + + public string EncodeOutputFunction { get; } = encodeOutputFunction; + + public string LoadBackdropExpression { get; } = loadBackdropExpression; + + public string LoadBrushExpression { get; } = loadBrushExpression; + + public string StoreOutputStatement { get; } = storeOutputStatement; + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/CompositionShaderSnippets.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/CompositionShaderSnippets.cs new file mode 100644 index 000000000..cca89d98d --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/CompositionShaderSnippets.cs @@ -0,0 +1,133 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Shared WGSL function snippets used by multiple compute shaders that perform pixel blending +/// and alpha composition (e.g., and ). +/// +internal static class CompositionShaderSnippets +{ + /// + /// WGSL functions for unpremultiplying alpha, blending colors by mode, + /// and compositing pixels using Porter-Duff alpha composition. + /// + internal const string BlendAndCompose = + """ + fn unpremultiply(rgb: vec3, alpha: f32) -> vec3 { + if (alpha <= 0.0) { + return vec3(0.0); + } + + return rgb / alpha; + } + + fn blend_color(backdrop: vec3, source: vec3, mode: u32) -> vec3 { + switch mode { + case 1u: { + return backdrop * source; + } + case 2u: { + return backdrop + source; + } + case 3u: { + return backdrop - source; + } + case 4u: { + return 1.0 - ((1.0 - backdrop) * (1.0 - source)); + } + case 5u: { + return min(backdrop, source); + } + case 6u: { + return max(backdrop, source); + } + case 7u: { + return select( + 2.0 * backdrop * source, + 1.0 - (2.0 * (1.0 - backdrop) * (1.0 - source)), + backdrop >= vec3(0.5)); + } + case 8u: { + return select( + 2.0 * backdrop * source, + 1.0 - (2.0 * (1.0 - backdrop) * (1.0 - source)), + source >= vec3(0.5)); + } + default: { + return source; + } + } + } + + fn compose_pixel(destination_premul: vec4, source: vec4, color_mode: u32, alpha_mode: u32) -> vec4 { + let destination_alpha = destination_premul.a; + let destination_rgb_straight = unpremultiply(destination_premul.rgb, destination_alpha); + let source_alpha = source.a; + let source_rgb = source.rgb; + let source_premul = source_rgb * source_alpha; + let forward_blend = blend_color(destination_rgb_straight, source_rgb, color_mode); + let reverse_blend = blend_color(source_rgb, destination_rgb_straight, color_mode); + let shared_alpha = source_alpha * destination_alpha; + + switch alpha_mode { + case 1u: { + return vec4(source_premul, source_alpha); + } + case 2u: { + let premul = (destination_rgb_straight * (destination_alpha - shared_alpha)) + (forward_blend * shared_alpha); + return vec4(premul, destination_alpha); + } + case 3u: { + let alpha = source_alpha * destination_alpha; + return vec4(source_premul * destination_alpha, alpha); + } + case 4u: { + let alpha = source_alpha * (1.0 - destination_alpha); + return vec4(source_premul * (1.0 - destination_alpha), alpha); + } + case 5u: { + return destination_premul; + } + case 6u: { + let premul = (source_rgb * (source_alpha - shared_alpha)) + (reverse_blend * shared_alpha); + return vec4(premul, source_alpha); + } + case 7u: { + let alpha = destination_alpha + source_alpha - shared_alpha; + let premul = + (source_rgb * (source_alpha - shared_alpha)) + + (destination_rgb_straight * (destination_alpha - shared_alpha)) + + (reverse_blend * shared_alpha); + return vec4(premul, alpha); + } + case 8u: { + let alpha = destination_alpha * source_alpha; + return vec4(destination_premul.rgb * source_alpha, alpha); + } + case 9u: { + let alpha = destination_alpha * (1.0 - source_alpha); + return vec4(destination_premul.rgb * (1.0 - source_alpha), alpha); + } + case 10u: { + return vec4(0.0, 0.0, 0.0, 0.0); + } + case 11u: { + let source_term = source_premul * (1.0 - destination_alpha); + let destination_term = destination_premul.rgb * (1.0 - source_alpha); + let alpha = source_alpha * (1.0 - destination_alpha) + destination_alpha * (1.0 - source_alpha); + return vec4(source_term + destination_term, alpha); + } + default: { + let alpha = source_alpha + destination_alpha - shared_alpha; + let premul = + (destination_rgb_straight * (destination_alpha - shared_alpha)) + + (source_rgb * (source_alpha - shared_alpha)) + + (forward_blend * shared_alpha); + return vec4(premul, alpha); + } + } + } + """; +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/CsrCountComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrCountComputeShader.cs new file mode 100644 index 000000000..75f7045ad --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrCountComputeShader.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// GPU compute shader that counts edges per CSR band. +/// Each thread processes one edge and atomically increments band counts +/// for each 16-row band the edge overlaps. +/// +internal static class CsrCountComputeShader +{ + private static readonly byte[] CodeBytes = + [ + .. """ + struct Edge { + x0: i32, + y0: i32, + x1: i32, + y1: i32, + min_row: i32, + max_row: i32, + csr_band_offset: u32, + definition_edge_start: u32, + } + + struct CsrConfig { + total_edge_count: u32, + }; + + @group(0) @binding(0) var edges: array; + @group(0) @binding(1) var band_counts: array>; + @group(0) @binding(2) var config: CsrConfig; + + @compute @workgroup_size(256, 1, 1) + fn cs_main(@builtin(global_invocation_id) gid: vec3) { + let edge_idx = gid.x; + if (edge_idx >= config.total_edge_count) { + return; + } + let edge = edges[edge_idx]; + if (edge.min_row > edge.max_row) { + return; + } + let min_band = edge.min_row / 16; + let max_band = edge.max_row / 16; + for (var band = min_band; band <= max_band; band++) { + atomicAdd(&band_counts[edge.csr_band_offset + u32(band)], 1u); + } + } + """u8, + 0 + ]; + + /// Gets the WGSL source for this shader as a null-terminated UTF-8 span. + public static ReadOnlySpan Code => CodeBytes; +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixBlockScanComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixBlockScanComputeShader.cs new file mode 100644 index 000000000..2b8dfc300 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixBlockScanComputeShader.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Phase 2 of the parallel CSR prefix sum: a single workgroup performs an +/// in-place exclusive prefix sum over the block_sums array from phase 1. +/// Supports up to 65536 blocks (256 * 256 = 16M bands). +/// +internal static class CsrPrefixBlockScanComputeShader +{ + private static readonly byte[] CodeBytes = + [ + .. """ + struct PrefixConfig { + block_count: u32, + }; + + @group(0) @binding(0) var block_sums: array; + @group(0) @binding(1) var prefix_config: PrefixConfig; + + var shared_data: array; + + @compute @workgroup_size(256, 1, 1) + fn cs_main(@builtin(local_invocation_id) local_id: vec3) { + let tid = local_id.x; + let block_count = prefix_config.block_count; + + // Each thread processes multiple chunks of 256 blocks sequentially. + // This handles up to 65536 blocks (256 threads * 256 elements each). + var running_total = 0u; + var chunk_start = 0u; + loop { + if (chunk_start >= block_count) { + break; + } + + let global_index = chunk_start + tid; + var value = 0u; + if (global_index < block_count) { + value = block_sums[global_index]; + } + shared_data[tid] = value; + workgroupBarrier(); + + // Up-sweep. + for (var stride = 1u; stride < 256u; stride = stride * 2u) { + let index = (tid + 1u) * stride * 2u - 1u; + if (index < 256u) { + shared_data[index] = shared_data[index] + shared_data[index - stride]; + } + workgroupBarrier(); + } + + // Store chunk total and clear for down-sweep. + var chunk_total = 0u; + if (tid == 0u) { + chunk_total = shared_data[255]; + shared_data[255] = 0u; + } + workgroupBarrier(); + + // Down-sweep. + for (var stride = 128u; stride >= 1u; stride = stride / 2u) { + let index = (tid + 1u) * stride * 2u - 1u; + if (index < 256u) { + let temp = shared_data[index - stride]; + shared_data[index - stride] = shared_data[index]; + shared_data[index] = shared_data[index] + temp; + } + workgroupBarrier(); + } + + // Write back with running total offset. + if (global_index < block_count) { + block_sums[global_index] = shared_data[tid] + running_total; + } + workgroupBarrier(); + + // Broadcast chunk_total from thread 0 for next iteration. + if (tid == 0u) { + shared_data[0] = chunk_total; + } + workgroupBarrier(); + running_total = running_total + shared_data[0]; + workgroupBarrier(); + + chunk_start = chunk_start + 256u; + } + } + """u8, + 0 + ]; + + /// Gets the WGSL source for this shader as a null-terminated UTF-8 span. + public static ReadOnlySpan Code => CodeBytes; +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixLocalComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixLocalComputeShader.cs new file mode 100644 index 000000000..383abfada --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixLocalComputeShader.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Phase 1 of the parallel CSR prefix sum: each workgroup computes a local +/// exclusive prefix sum over 256 band counts, writes per-band offsets, and +/// stores the workgroup total into a block_sums buffer. +/// +internal static class CsrPrefixLocalComputeShader +{ + /// + /// The number of tiles processed by each workgroup. + /// + public const int TilesPerWorkgroup = 256; + + private static readonly byte[] CodeBytes = + [ + .. """ + struct DispatchConfig { + target_width: u32, + target_height: u32, + tile_count_x: u32, + tile_count_y: u32, + tile_count: u32, + command_count: u32, + source_origin_x: u32, + source_origin_y: u32, + output_origin_x: u32, + output_origin_y: u32, + width_in_bins: u32, + height_in_bins: u32, + bin_count: u32, + partition_count: u32, + binning_size: u32, + bin_data_start: u32, + }; + + @group(0) @binding(0) var tile_counts: array>; + @group(0) @binding(1) var tile_starts: array; + @group(0) @binding(2) var block_sums: array; + @group(0) @binding(3) var dispatch_config: DispatchConfig; + + var shared_data: array; + + @compute @workgroup_size(256, 1, 1) + fn cs_main( + @builtin(local_invocation_id) local_id: vec3, + @builtin(workgroup_id) wg_id: vec3 + ) { + let tid = local_id.x; + let global_index = wg_id.x * 256u + tid; + + // Load tile count (0 if out of range). + var value = 0u; + if (global_index < dispatch_config.tile_count) { + value = atomicLoad(&tile_counts[global_index]); + } + shared_data[tid] = value; + workgroupBarrier(); + + // Up-sweep (reduce) phase. + for (var stride = 1u; stride < 256u; stride = stride * 2u) { + let index = (tid + 1u) * stride * 2u - 1u; + if (index < 256u) { + shared_data[index] = shared_data[index] + shared_data[index - stride]; + } + workgroupBarrier(); + } + + // Store total and clear last element for down-sweep. + if (tid == 0u) { + block_sums[wg_id.x] = shared_data[255]; + shared_data[255] = 0u; + } + workgroupBarrier(); + + // Down-sweep phase. + for (var stride = 128u; stride >= 1u; stride = stride / 2u) { + let index = (tid + 1u) * stride * 2u - 1u; + if (index < 256u) { + let temp = shared_data[index - stride]; + shared_data[index - stride] = shared_data[index]; + shared_data[index] = shared_data[index] + temp; + } + workgroupBarrier(); + } + + // Write exclusive prefix sum to output. + if (global_index < dispatch_config.tile_count) { + tile_starts[global_index] = shared_data[tid]; + } + } + """u8, + 0 + ]; + + /// Gets the WGSL source for this shader as a null-terminated UTF-8 span. + public static ReadOnlySpan Code => CodeBytes; +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixPropagateComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixPropagateComputeShader.cs new file mode 100644 index 000000000..1e310025a --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrPrefixPropagateComputeShader.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Phase 3 of the parallel CSR prefix sum: each workgroup adds its +/// block prefix from block_sums to all CSR offsets in its range. +/// Workgroup 0 is skipped (its prefix is 0). +/// +internal static class CsrPrefixPropagateComputeShader +{ + private static readonly byte[] CodeBytes = + [ + .. """ + struct DispatchConfig { + target_width: u32, + target_height: u32, + tile_count_x: u32, + tile_count_y: u32, + tile_count: u32, + command_count: u32, + source_origin_x: u32, + source_origin_y: u32, + output_origin_x: u32, + output_origin_y: u32, + width_in_bins: u32, + height_in_bins: u32, + bin_count: u32, + partition_count: u32, + binning_size: u32, + bin_data_start: u32, + }; + + @group(0) @binding(0) var block_sums: array; + @group(0) @binding(1) var tile_starts: array; + @group(0) @binding(2) var dispatch_config: DispatchConfig; + + @compute @workgroup_size(256, 1, 1) + fn cs_main( + @builtin(local_invocation_id) local_id: vec3, + @builtin(workgroup_id) wg_id: vec3 + ) { + if (wg_id.x == 0u) { + return; + } + + let global_index = wg_id.x * 256u + local_id.x; + if (global_index < dispatch_config.tile_count) { + tile_starts[global_index] = tile_starts[global_index] + block_sums[wg_id.x]; + } + } + """u8, + 0 + ]; + + /// Gets the WGSL source for this shader as a null-terminated UTF-8 span. + public static ReadOnlySpan Code => CodeBytes; +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/CsrScatterComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrScatterComputeShader.cs new file mode 100644 index 000000000..b3ab2b409 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/CsrScatterComputeShader.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// GPU compute shader that scatters edge indices into CSR buckets. +/// Each thread processes one edge. For each band the edge overlaps, +/// it atomically claims a slot in csr_indices via a write cursor. +/// +internal static class CsrScatterComputeShader +{ + private static readonly byte[] CodeBytes = + [ + .. """ + struct Edge { + x0: i32, + y0: i32, + x1: i32, + y1: i32, + min_row: i32, + max_row: i32, + csr_band_offset: u32, + definition_edge_start: u32, + } + + struct CsrConfig { + total_edge_count: u32, + }; + + @group(0) @binding(0) var edges: array; + @group(0) @binding(1) var csr_offsets: array; + @group(0) @binding(2) var write_cursors: array>; + @group(0) @binding(3) var csr_indices: array; + @group(0) @binding(4) var config: CsrConfig; + + @compute @workgroup_size(256, 1, 1) + fn cs_main(@builtin(global_invocation_id) gid: vec3) { + let edge_idx = gid.x; + if (edge_idx >= config.total_edge_count) { + return; + } + let edge = edges[edge_idx]; + if (edge.min_row > edge.max_row) { + return; + } + let local_idx = edge_idx - edge.definition_edge_start; + let min_band = edge.min_row / 16; + let max_band = edge.max_row / 16; + for (var band = min_band; band <= max_band; band++) { + let band_offset = edge.csr_band_offset + u32(band); + let offset = csr_offsets[band_offset]; + let slot = atomicAdd(&write_cursors[band_offset], 1u); + csr_indices[offset + slot] = local_idx; + } + } + """u8, + 0 + ]; + + /// Gets the WGSL source for this shader as a null-terminated UTF-8 span. + public static ReadOnlySpan Code => CodeBytes; +} diff --git a/src/ImageSharp.Drawing.WebGPU/Shaders/StrokeExpandComputeShader.cs b/src/ImageSharp.Drawing.WebGPU/Shaders/StrokeExpandComputeShader.cs new file mode 100644 index 000000000..1d30116d7 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/Shaders/StrokeExpandComputeShader.cs @@ -0,0 +1,311 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// GPU compute shader that expands stroke centerline edges into outline polygon edges. +/// Each thread processes one centerline edge and writes outline edges (side/join/cap) +/// to the output region of the edge buffer using an atomic counter for slot allocation. +/// The generated outline edges are then rasterized by the composite shader's fill path. +/// +internal static class StrokeExpandComputeShader +{ + private static readonly byte[] CodeBytes = + [ + .. """ + struct Edge { + x0: i32, + y0: i32, + x1: i32, + y1: i32, + flags: i32, + adj_x: i32, + adj_y: i32, + } + + struct StrokeExpandCommand { + input_start: u32, + input_count: u32, + output_start: u32, + output_max: u32, + half_width: f32, + line_cap: u32, + line_join: u32, + miter_limit: f32, + } + + struct StrokeExpandConfig { + total_input_edges: u32, + command_count: u32, + } + + @group(0) @binding(0) var edges: array; + @group(0) @binding(1) var commands: array; + @group(0) @binding(2) var config: StrokeExpandConfig; + @group(0) @binding(3) var output_counters: array>; + + const FIXED_ONE: i32 = 256; + + // Edge descriptor flags (matches C# GpuEdge.Flags bit layout). + const EDGE_JOIN: i32 = 32; + const EDGE_CAP_START: i32 = 64; + const EDGE_CAP_END: i32 = 128; + + // LineCap enum values. + const CAP_BUTT: u32 = 0u; + const CAP_SQUARE: u32 = 1u; + const CAP_ROUND: u32 = 2u; + + // LineJoin enum values. + const JOIN_MITER: u32 = 0u; + const JOIN_MITER_REVERT: u32 = 1u; + const JOIN_ROUND: u32 = 2u; + const JOIN_BEVEL: u32 = 3u; + const JOIN_MITER_ROUND: u32 = 4u; + + var p_cmd: StrokeExpandCommand; + var p_cmd_idx: u32; + + fn emit_outline_edge(ex0: i32, ey0: i32, ex1: i32, ey1: i32) { + if ey0 == ey1 { return; } + let slot = atomicAdd(&output_counters[p_cmd_idx], 1u); + let idx = p_cmd.output_start + slot; + if idx >= p_cmd.output_max { return; } + var out_edge: Edge; + out_edge.x0 = ex0; + out_edge.y0 = ey0; + out_edge.x1 = ex1; + out_edge.y1 = ey1; + out_edge.flags = 0; + out_edge.adj_x = 0; + out_edge.adj_y = 0; + edges[idx] = out_edge; + } + + fn generate_side_edges(edge: Edge, hw_fp: f32) { + let fdx = f32(edge.x1 - edge.x0); + let fdy = f32(edge.y1 - edge.y0); + let flen = sqrt(fdx * fdx + fdy * fdy); + if flen < 1.0 { return; } + let nxf = -fdy / flen * hw_fp; + let nyf = fdx / flen * hw_fp; + let x0f = f32(edge.x0); + let y0f = f32(edge.y0); + let x1f = f32(edge.x1); + let y1f = f32(edge.y1); + emit_outline_edge( + i32(round(x0f + nxf)), i32(round(y0f + nyf)), + i32(round(x1f + nxf)), i32(round(y1f + nyf))); + emit_outline_edge( + i32(round(x1f - nxf)), i32(round(y1f - nyf)), + i32(round(x0f - nxf)), i32(round(y0f - nyf))); + } + + fn generate_join_edges(edge: Edge, hw_fp: f32, line_join: u32, miter_limit: f32) { + let vx = f32(edge.x0); + let vy = f32(edge.y0); + let dx1 = vx - f32(edge.x1); + let dy1 = vy - f32(edge.y1); + let len1 = sqrt(dx1 * dx1 + dy1 * dy1); + if len1 < 1.0 { return; } + let dx2 = f32(edge.adj_x) - vx; + let dy2 = f32(edge.adj_y) - vy; + let len2 = sqrt(dx2 * dx2 + dy2 * dy2); + if len2 < 1.0 { return; } + + let nx1 = -dy1 / len1; let ny1 = dx1 / len1; + let nx2 = -dy2 / len2; let ny2 = dx2 / len2; + let cross = dx1 * dy2 - dy1 * dx2; + + var oax: f32; var oay: f32; var obx: f32; var oby: f32; + var iax: f32; var iay: f32; var ibx: f32; var iby: f32; + if cross > 0.0 { + oax = vx - nx1 * hw_fp; oay = vy - ny1 * hw_fp; + obx = vx - nx2 * hw_fp; oby = vy - ny2 * hw_fp; + iax = vx + nx1 * hw_fp; iay = vy + ny1 * hw_fp; + ibx = vx + nx2 * hw_fp; iby = vy + ny2 * hw_fp; + } else { + oax = vx + nx1 * hw_fp; oay = vy + ny1 * hw_fp; + obx = vx + nx2 * hw_fp; oby = vy + ny2 * hw_fp; + iax = vx - nx1 * hw_fp; iay = vy - ny1 * hw_fp; + ibx = vx - nx2 * hw_fp; iby = vy - ny2 * hw_fp; + } + + var ofx: f32; var ofy: f32; var otx: f32; var oty: f32; + var ifx: f32; var ify: f32; var itx: f32; var ity: f32; + if cross > 0.0 { + ofx = obx; ofy = oby; otx = oax; oty = oay; + ifx = iax; ify = iay; itx = ibx; ity = iby; + } else { + ofx = oax; ofy = oay; otx = obx; oty = oby; + ifx = ibx; ify = iby; itx = iax; ity = iay; + } + + // Inner join: always bevel. + emit_outline_edge(i32(round(ifx)), i32(round(ify)), i32(round(itx)), i32(round(ity))); + + // Outer join. + var miter_handled = false; + if line_join == JOIN_MITER || line_join == JOIN_MITER_REVERT || line_join == JOIN_MITER_ROUND { + let ux1 = dx1 / len1; let uy1 = dy1 / len1; + let ux2 = dx2 / len2; let uy2 = dy2 / len2; + let denom = ux1 * uy2 - uy1 * ux2; + if abs(denom) > 1e-4 { + let dpx = obx - oax; let dpy = oby - oay; + let t = (dpx * uy2 - dpy * ux2) / denom; + let mx = oax + t * ux1; let my = oay + t * uy1; + let mdx = mx - vx; let mdy = my - vy; + let miter_dist = sqrt(mdx * mdx + mdy * mdy); + let limit = hw_fp * miter_limit; + if miter_dist <= limit { + emit_outline_edge(i32(round(ofx)), i32(round(ofy)), i32(round(mx)), i32(round(my))); + emit_outline_edge(i32(round(mx)), i32(round(my)), i32(round(otx)), i32(round(oty))); + miter_handled = true; + } else if line_join == JOIN_MITER { + let bdx = (oax + obx) * 0.5 - vx; + let bdy = (oay + oby) * 0.5 - vy; + let bdist = sqrt(bdx * bdx + bdy * bdy); + let blend = clamp((limit - bdist) / (miter_dist - bdist), 0.0, 1.0); + let cx1 = ofx + (mx - ofx) * blend; let cy1 = ofy + (my - ofy) * blend; + let cx2 = otx + (mx - otx) * blend; let cy2 = oty + (my - oty) * blend; + emit_outline_edge(i32(round(ofx)), i32(round(ofy)), i32(round(cx1)), i32(round(cy1))); + emit_outline_edge(i32(round(cx1)), i32(round(cy1)), i32(round(cx2)), i32(round(cy2))); + emit_outline_edge(i32(round(cx2)), i32(round(cy2)), i32(round(otx)), i32(round(oty))); + miter_handled = true; + } + } + } + if !miter_handled { + if line_join == JOIN_ROUND || line_join == JOIN_MITER_ROUND { + let sa = atan2(ofy - vy, ofx - vx); + let ea = atan2(oty - vy, otx - vx); + var sweep = ea - sa; + if sweep > 3.14159265 { sweep -= 2.0 * 3.14159265; } + if sweep < -3.14159265 { sweep += 2.0 * 3.14159265; } + let rpx = hw_fp / f32(FIXED_ONE); + let steps = max(4u, u32(ceil(abs(sweep) * rpx * 0.5))); + let da = sweep / f32(steps); + var pax = ofx; var pay = ofy; + for (var s = 1u; s <= steps; s++) { + var cax: f32; var cay: f32; + if s == steps { cax = otx; cay = oty; } + else { + let a = sa + da * f32(s); + cax = vx + cos(a) * hw_fp; + cay = vy + sin(a) * hw_fp; + } + emit_outline_edge(i32(round(pax)), i32(round(pay)), i32(round(cax)), i32(round(cay))); + pax = cax; pay = cay; + } + } else { + emit_outline_edge(i32(round(ofx)), i32(round(ofy)), i32(round(otx)), i32(round(oty))); + } + } + } + + fn generate_cap_edges(edge: Edge, hw_fp: f32, line_cap: u32, is_start: bool) { + let cx = f32(edge.x0); let cy = f32(edge.y0); + let ax = f32(edge.x1); let ay = f32(edge.y1); + var dx: f32; var dy: f32; + if is_start { dx = ax - cx; dy = ay - cy; } + else { dx = cx - ax; dy = cy - ay; } + let len = sqrt(dx * dx + dy * dy); + if len < 1.0 { return; } + let dir_x = dx / len; let dir_y = dy / len; + let nx = -dir_y * hw_fp; let ny = dir_x * hw_fp; + let lx = cx + nx; let ly = cy + ny; + let rx = cx - nx; let ry = cy - ny; + + if line_cap == CAP_BUTT { + if is_start { + emit_outline_edge(i32(round(rx)), i32(round(ry)), i32(round(lx)), i32(round(ly))); + } else { + emit_outline_edge(i32(round(lx)), i32(round(ly)), i32(round(rx)), i32(round(ry))); + } + } else if line_cap == CAP_SQUARE { + var ox: f32; var oy: f32; + if is_start { ox = -dir_x * hw_fp; oy = -dir_y * hw_fp; } + else { ox = dir_x * hw_fp; oy = dir_y * hw_fp; } + let lxe = lx + ox; let lye = ly + oy; + let rxe = rx + ox; let rye = ry + oy; + if is_start { + emit_outline_edge(i32(round(rx)), i32(round(ry)), i32(round(rxe)), i32(round(rye))); + emit_outline_edge(i32(round(rxe)), i32(round(rye)), i32(round(lxe)), i32(round(lye))); + emit_outline_edge(i32(round(lxe)), i32(round(lye)), i32(round(lx)), i32(round(ly))); + } else { + emit_outline_edge(i32(round(lx)), i32(round(ly)), i32(round(lxe)), i32(round(lye))); + emit_outline_edge(i32(round(lxe)), i32(round(lye)), i32(round(rxe)), i32(round(rye))); + emit_outline_edge(i32(round(rxe)), i32(round(rye)), i32(round(rx)), i32(round(ry))); + } + } else if line_cap == CAP_ROUND { + var sa: f32; var sx: f32; var sy: f32; var ex: f32; var ey: f32; + if is_start { + sa = atan2(ry - cy, rx - cx); + sx = rx; sy = ry; ex = lx; ey = ly; + } else { + sa = atan2(ly - cy, lx - cx); + sx = lx; sy = ly; ex = rx; ey = ry; + } + var sweep = atan2(ey - cy, ex - cx) - sa; + if sweep > 0.0 { sweep -= 2.0 * 3.14159265; } + let rpx = hw_fp / f32(FIXED_ONE); + let steps = max(4u, u32(ceil(abs(sweep) * rpx * 0.5))); + let da = sweep / f32(steps); + var pax = sx; var pay = sy; + for (var s = 1u; s <= steps; s++) { + var cax: f32; var cay: f32; + if s == steps { cax = ex; cay = ey; } + else { + let a = sa + da * f32(s); + cax = cx + cos(a) * hw_fp; + cay = cy + sin(a) * hw_fp; + } + emit_outline_edge(i32(round(pax)), i32(round(pay)), i32(round(cax)), i32(round(cay))); + pax = cax; pay = cay; + } + } + } + + @compute @workgroup_size(256, 1, 1) + fn cs_main(@builtin(global_invocation_id) gid: vec3) { + let thread_idx = gid.x; + if thread_idx >= config.total_input_edges { return; } + + // Find which command this thread's edge belongs to. + var running = 0u; + var cmd_idx = 0u; + for (var c = 0u; c < config.command_count; c++) { + let cmd = commands[c]; + if thread_idx < running + cmd.input_count { + cmd_idx = c; + break; + } + running += cmd.input_count; + } + + let cmd = commands[cmd_idx]; + p_cmd = cmd; + p_cmd_idx = cmd_idx; + let local_idx = thread_idx - running; + let edge = edges[cmd.input_start + local_idx]; + let hw_fp = cmd.half_width * f32(FIXED_ONE); + let flags = edge.flags; + + if (flags & EDGE_JOIN) != 0 { + generate_join_edges(edge, hw_fp, cmd.line_join, cmd.miter_limit); + } else if (flags & EDGE_CAP_START) != 0 { + generate_cap_edges(edge, hw_fp, cmd.line_cap, true); + } else if (flags & EDGE_CAP_END) != 0 { + generate_cap_edges(edge, hw_fp, cmd.line_cap, false); + } else { + generate_side_edges(edge, hw_fp); + } + } + """u8, + 0 + ]; + + /// Gets the WGSL source for this shader as a null-terminated UTF-8 span. + public static ReadOnlySpan Code => CodeBytes; +} diff --git a/src/ImageSharp.Drawing.WebGPU/WEBGPU_BACKEND_PROCESS.md b/src/ImageSharp.Drawing.WebGPU/WEBGPU_BACKEND_PROCESS.md new file mode 100644 index 000000000..62fc7f624 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WEBGPU_BACKEND_PROCESS.md @@ -0,0 +1,289 @@ +# WebGPU Backend Process + +This document describes the runtime flows used by `WebGPUDrawingBackend` for scene flushing and layer compositing. + +## End-to-End Flow + +```text +DrawingCanvasBatcher.Flush() + -> IDrawingBackend.FlushCompositions(scene) + -> if target has no NativeSurface: delegate to DefaultDrawingBackend directly + -> capability checks + -> TryGetCompositeTextureFormat + -> AreAllCompositionBrushesSupported + -> if unsupported: staging fallback (see Fallback Behavior) + -> CompositionScenePlanner.CreatePreparedBatches(commands, targetBounds) + -> clip each command to target bounds + -> group contiguous commands by DefinitionKey + -> keep prepared destination/source offsets + -> compute scene command count + composition bounds + -> if no visible commands: return + -> acquire one WebGPUFlushContext for the scene + -> TryRenderPreparedFlush + -> ensure command encoder (single encoder reused for the scene) + -> use target texture view directly as backdrop source (no copy) + -> allocate transient output texture for composition bounds + -> deduplicate coverage definitions across batches via CoverageDefinitionIdentity + (keyed by path, interest, intersection rule, rasterization mode, sampling origin, antialias threshold) + -> TryCreateEdgeBuffer (CPU-side edge preparation) + -> for each unique coverage definition: + -> path.Flatten() to iterate flattened vertices + -> build fixed-point (24.8) GpuEdge via MemoryAllocator (IMemoryOwner) + -> compute min_row/max_row per edge, clamped to interest + -> build CSR (Compressed Sparse Row) band-to-edge mapping: + 1) count edges per 16-row band + 2) exclusive prefix sum over band counts + 3) scatter edge indices into CSR index array + -> merge per-definition edges into single buffer with metadata stamps + -> single-definition fast path: stamp in-place + -> multi-definition: merge via Span.CopyTo + -> upload edge buffer via dirty-range detection (word-by-word diff) + -> upload merged CSR offsets and indices via QueueWriteBuffer + -> TryDispatchPreparedCompositeCommands + -> build per-command PreparedCompositeParameters (destination, edge placement, + brush type/color/region, blend mode, composition mode) + -> upload parameters + dispatch config via QueueWriteBuffer + -> single compute dispatch: CompositeComputeShader + -> workgroup size: 16x16 (one tile per workgroup) + -> dispatched as (tileCountX, tileCountY, 1) + -> each workgroup: + -> loads backdrop pixel from target texture + -> for each command overlapping this tile: + -> clears workgroup shared memory (tile_cover, tile_area, tile_start_cover) + -> cooperatively rasterizes edges from CSR bands using fixed-point scanline math + -> X-range spatial filter: edges left of tile only update start_cover + -> barrier, then each thread accumulates its coverage from shared memory + -> applies fill rule (non-zero or even-odd) + -> if aliased mode: snaps coverage to binary using antialias threshold + -> samples brush (see Brush Types below) + -> composes pixel using Porter-Duff alpha composition + color blend mode + -> writes final pixel to output texture + -> copy composited output texture region back into target texture + -> TryFinalizeFlush + -> finish encoder + single QueueSubmit (non-blocking) + -> on any GPU failure: scene-scoped fallback (DefaultDrawingBackend) +``` + +## Stroke Processing + +For stroke definitions (`CompositionCoverageDefinition.IsStroke`), the backend +performs stroke expansion on the GPU using `StrokeExpandComputeShader`: + +1. **Dash splitting** (CPU): If the definition has a dash pattern, `SplitPathExtensions.GenerateDashes()` + (shared with `DefaultDrawingBackend` in the core project) segments the centerline into + open dash sub-paths before edge building. + +2. **Centerline edge building** (CPU): `path.Flatten()` produces contour vertices. + Centerline edges are built as `GpuEdge` structs with `StrokeEdgeFlags` indicating + the edge type (`None` for side edges, `Join`, `CapStart`, `CapEnd`). Join edges + carry adjacent vertex coordinates in `AdjX`/`AdjY`. Centerline edges are band-sorted + with Y expansion of `halfWidth * max(miterLimit, 1)`. + +3. **GPU stroke expansion**: One `StrokeExpandCommand` per band dispatches the compute + shader. Each thread expands one centerline edge into outline edges written to + per-band output slots via atomic counters. Output buffer size is computed by + `ComputeOutlineEdgesPerCenterline()` which accounts for join/cap type and arc + step count for round joins/caps. + +4. **Rasterization**: The generated outline edges are band-sorted and rasterized + by the composite shader's fill path (same fixed-point scanline rasterizer). + +## GPU Buffer Layout + +### Edge Buffer (`coverage-aggregated-edges`) + +Each edge is a 32-byte `GpuEdge` struct (sequential layout): + +| Field | Type | Description | +|---|---|---| +| X0, Y0 | i32 | Start point in 24.8 fixed-point | +| X1, Y1 | i32 | End point in 24.8 fixed-point | +| Flags | StrokeEdgeFlags | Stroke edge type (None/Join/CapStart/CapEnd) | +| AdjX, AdjY | i32 | Auxiliary coords (join adjacent vertex) | + +### CSR Buffers + +- `csr-offsets`: `array` - per-band prefix sum. `offsets[band]..offsets[band+1]` gives the range of edge indices for that 16-row band. +- `csr-indices`: `array` - edge indices within each band, ordered by band. + +### Command Parameters + +Each `PreparedCompositeParameters` struct (32 × u32 = 128 bytes) contains destination rectangle, edge placement (start, fill rule, CSR offsets start, band count), brush type and configuration (gp0–gp7), blend/composition mode, blend percentage, rasterization mode (0 = antialiased, 1 = aliased), antialias threshold (float as u32 bitcast), and color stop buffer references (stops_offset, stop_count). + +### Dispatch Config + +`PreparedCompositeDispatchConfig` contains target dimensions, tile counts, source/output origins, and command count. + +## Brush Types + +All brush types are handled natively on the GPU. The backend passes raw brush properties via gp0–gp7 fields; derived values (trig, distances, etc.) are computed per-pixel in the shader. + +| Constant | Type | gp fields | +|---|---|---| +| `BRUSH_SOLID` (0) | Solid color | gp0–3 = RGBA | +| `BRUSH_IMAGE` (1) | Image texture | brush_origin/region fields + texture binding | +| `BRUSH_LINEAR_GRADIENT` (2) | Linear gradient | gp0–3 = start.xy, end.xy; gp4 = repetition mode | +| `BRUSH_RADIAL_GRADIENT` (3) | Radial (single circle) | gp0–1 = center; gp2 = radius; gp4 = repetition mode | +| `BRUSH_RADIAL_GRADIENT_TWO_CIRCLE` (4) | Radial (two circle) | gp0–3 = c0.xy, c1.xy; gp4–5 = r0, r1; gp6 = repetition mode | +| `BRUSH_ELLIPTIC_GRADIENT` (5) | Elliptic gradient | gp0–3 = center.xy, refEnd.xy; gp4 = axis ratio; gp5 = repetition mode | +| `BRUSH_SWEEP_GRADIENT` (6) | Sweep gradient | gp0–3 = center.xy, startAngleDeg, endAngleDeg; gp4 = repetition mode | +| `BRUSH_PATTERN` (7) | Pattern | gp0–1 = width, height; gp2–3 = origin; cells packed in color stops buffer | +| `BRUSH_RECOLOR` (8) | Recolor | gp0–3 = source RGBA; gp4–7 = target RGBA; stops_offset = threshold | + +Gradient color stops are packed into a shared storage buffer (binding 7). Each stop is 5 × f32 (ratio, R, G, B, A). The `stops_offset` and `stop_count` fields in the command parameters index into this buffer. Color stop interpolation in the shader is an exact copy of the C# `GradientBrushApplicator.GetGradientSegment` + lerp logic - including unclamped `t` values, stable sort order, and per-mode repetition semantics. + +Color stops are sorted by ratio in the `GradientBrush` constructor using a stable insertion sort to preserve the order of equal-ratio stops. + +## Shader Bindings (CompositeComputeShader) + +| Binding | Type | Description | +|---|---|---| +| 0 | `storage, read` | Edge buffer (`array`) | +| 1 | `texture_2d` | Backdrop texture (target) | +| 2 | `texture_2d` | Brush texture (image brush or same as backdrop) | +| 3 | `texture_storage_2d, write` | Output texture | +| 4 | `storage, read` | Command parameters (`array`) | +| 5 | `uniform` | Dispatch config | +| 6 | `storage, read` | Band offsets (`array`) | +| 7 | `storage, read` | Color stops / pattern buffer (`array`) | + +## Context and Resource Lifetime + +- `WebGPUFlushContext` is created once per `FlushCompositions` execution and disposed at the end. +- The same command encoder is reused across all GPU operations in that flush. +- Transient textures, texture views, buffers, and bind groups are tracked in the flush context and released on dispose. +- Source image texture views are cached within the flush context to avoid duplicate uploads. +- CPU-side edge geometry (`IMemoryOwner`) is allocated via `MemoryAllocator` and disposed within the flush. +- Shared GPU buffers (edge buffer, CSR buffers, params buffer, dispatch config buffer) are managed by `DeviceState` with grow-only reuse across flushes. +- Edge upload uses dirty-range detection: compares current data word-by-word against a cached copy, uploading only the changed byte range via `QueueWriteBuffer`. + +## Destination Writeback + +- `FlushCompositions` performs one command-buffer submission (`QueueSubmit`) per scene flush. +- One GPU-side `CommandEncoderCopyTextureToTexture` from output into the target at composition bounds. No CPU stall. +- The WebGPU backend only operates on native GPU surfaces. CPU-backed frames are handled entirely by `DefaultDrawingBackend`. + +## Fallback Behavior + +### FlushCompositions + +1. **Non-native target**: If the target frame has no `NativeSurface`, delegate directly + to `DefaultDrawingBackend.FlushCompositions` (no staging, no upload). +2. **Native target, unsupported scene**: Pixel format has no GPU mapping, or a command + uses an unsupported brush (`PathGradientBrush`). Fallback: allocate a clean CPU + staging `Buffer2D`, run `DefaultDrawingBackend.FlushCompositions` on it, then + upload the staging region to the native target texture via `UploadTextureFromRegion`. +3. **GPU failure**: Any GPU operation fails during `TryRenderPreparedFlush` or + `TryFinalizeFlush`. Same staging + upload fallback as (2). + +### ComposeLayer + +1. **Non-native destination**: Delegate directly to `DefaultDrawingBackend.ComposeLayer`. +2. **Native destination, GPU compose fails**: Read both destination and source textures + from the GPU via `TryReadRegion`, compose on CPU via `DefaultDrawingBackend.ComposeLayer`, + then upload the composited destination back to the native texture. + +## Layer Compositing (ComposeLayer) + +`SaveLayer` on `DrawingCanvas` calls `IDrawingBackend.CreateLayerFrame` to allocate the layer. +When the parent target is a native GPU surface, `WebGPUDrawingBackend` creates a GPU-resident +texture for the layer (zero-copy). For CPU-backed parents it falls back to `DefaultDrawingBackend`. +On `Restore`, `IDrawingBackend.ComposeLayer` blends the layer onto the parent target. + +```text +DrawingCanvas.SaveLayer() + -> IDrawingBackend.CreateLayerFrame(parentTarget, width, height) + -> WebGPUDrawingBackend: GPU texture if parent is native, else CPU fallback + -> redirect draw commands to layer frame + +DrawingCanvas.Restore() / RestoreTo() + -> CompositeAndPopLayer(layerState) + -> Flush() current layer batcher + -> pop LayerData from layerDataStack + -> restore parent batcher + -> IDrawingBackend.ComposeLayer(source=layerFrame, destination=parentFrame, offset, options) + -> WebGPUDrawingBackend.ComposeLayer + -> TryComposeLayerGpu (requires destination with NativeSurface) + -> TryGetCompositeTextureFormat + -> destination must expose NativeSurface with WebGPU capability + -> TryAcquireSourceTexture: bind native GPU texture directly (zero-copy) + or upload CPU pixels to temporary GPU texture + -> create output texture sized to composite region + -> ComposeLayerComputeShader dispatch: + -> workgroup size 16x16 + -> each thread reads backdrop at (out_x + offset, out_y + offset) + -> reads source at (out_x, out_y) + -> applies layer opacity, blend mode, alpha composition + -> writes result to output at (out_x, out_y) + -> copy output back to destination texture at compositing offset + -> QueueSubmit + -> fallback: ComposeLayerFallback + -> TryReadRegion(destination) -> CPU staging image + -> TryReadRegion(source) -> CPU staging image + -> DefaultDrawingBackend.ComposeLayer on staging frames + -> UploadTextureFromRegion back to destination texture + -> IDrawingBackend.ReleaseFrameResources(layerFrame) + -> GPU frames: release texture + texture view + -> CPU frames: dispose Buffer2D +``` + +### Shader Bindings (ComposeLayerComputeShader) + +| Binding | Type | Description | +|---|---|---| +| 0 | `texture_2d` | Source layer texture (read) | +| 1 | `texture_2d` | Backdrop/destination texture (read) | +| 2 | `texture_storage_2d, write` | Output texture | +| 3 | `uniform` | `LayerConfig` (source dims, dest offset, blend mode, opacity) | + +### LayerConfig Uniform + +| Field | Type | Description | +|---|---|---| +| source_width, source_height | u32 | Source layer dimensions | +| dest_offset_x, dest_offset_y | i32 | Offset into backdrop texture | +| color_blend_mode | u32 | Porter-Duff color blend mode | +| alpha_composition_mode | u32 | Porter-Duff alpha composition mode | +| blend_percentage | u32 | Layer opacity (f32 bitcast to u32) | + +### Coordinate Spaces + +The output texture is sized to the composite region (intersection of source and +destination bounds). The shader uses local coordinates `(out_x, out_y)` for source +reads and output writes, and offset coordinates `(out_x + dest_offset, out_y + dest_offset)` +for backdrop reads. The final `CopyTextureRegion` places the output at the +compositing offset in the destination texture. + +## Shared Composition Functions + +`CompositionShaderSnippets.BlendAndCompose` contains shared WGSL functions used by both +`CompositeComputeShader` and `ComposeLayerComputeShader`: + +- `unpremultiply(rgb, alpha)` — converts premultiplied alpha to straight alpha +- `blend_color(backdrop, source, mode)` — 8 color blend modes (Normal, Multiply, Add, Subtract, Screen, Darken, Lighten, Overlay, HardLight) +- `compose_pixel(backdrop, source, blend_mode, compose_mode)` — full Porter-Duff alpha composition with 12 modes (Clear, Src, Dest, SrcOver, DestOver, SrcIn, DestIn, SrcOut, DestOut, SrcAtop, DestAtop, Xor) + +Both shaders include these functions via the `__BLEND_AND_COMPOSE__` template placeholder, +avoiding code duplication between the rasterize+composite and layer composite pipelines. + +## Shader Source + +`CompositeComputeShader` generates WGSL source per target texture format at runtime, substituting format-specific template tokens for texel decode/encode, backdrop/brush load, and output store. `ComposeLayerComputeShader` uses the same template approach with its own format traits. Generated source is cached by `TextureFormat` as null-terminated UTF-8 bytes. + +The following static WGSL shaders exist for the legacy CSR GPU pipeline but are not used in the current dispatch path (CSR is computed on CPU): +- `CsrCountComputeShader`, `CsrScatterComputeShader` +- `CsrPrefixLocalComputeShader`, `CsrPrefixBlockScanComputeShader`, `CsrPrefixPropagateComputeShader` + +## Performance Characteristics + +Coverage rasterization and compositing are fused into a single compute dispatch. Each 16x16 tile workgroup computes coverage inline using a fixed-point scanline rasterizer ported from `DefaultRasterizer`, operating on workgroup shared memory with atomic accumulation. This eliminates the coverage texture, its allocation, write/read bandwidth, and the pass barrier that a separate coverage dispatch would require. + +Edge preparation (path flattening, fixed-point conversion, CSR construction) runs on the CPU. The `path.Flatten()` cost is shared with the CPU rasterizer pipeline. CSR construction is three passes over the edge set: count, prefix sum, scatter. + +Both the CPU and GPU backends use per-band parallel stroke expansion - the CPU +via `DefaultRasterizer.RasterizeStrokeRows` and the GPU via +`StrokeExpandComputeShader`. Both share the same `StrokeEdgeFlags` enum and +`SplitPathExtensions.GenerateDashes` (in the core project). The CPU backend fuses stroke expansion +directly into the rasterizer's band loop, while the GPU backend uses a separate +compute dispatch that writes outline edges into pre-allocated per-band output +slots sized by `ComputeOutlineEdgesPerCenterline()`. diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUCompositeBindGroupLayoutFactory.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUCompositeBindGroupLayoutFactory.cs new file mode 100644 index 000000000..1d5ee20cf --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUCompositeBindGroupLayoutFactory.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Silk.NET.WebGPU; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Creates a bind group layout for WebGPU composition pipelines. +/// +/// The WebGPU API facade. +/// The device used to create resources. +/// The created bind-group layout. +/// The error message when creation fails. +/// if the layout was created; otherwise . +internal unsafe delegate bool WebGPUCompositeBindGroupLayoutFactory( + WebGPU api, + Device* device, + out BindGroupLayout* bindGroupLayout, + out string? error); diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.ComposeLayer.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.ComposeLayer.cs new file mode 100644 index 000000000..1d245f86f --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.ComposeLayer.cs @@ -0,0 +1,382 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Silk.NET.WebGPU; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using WgpuBuffer = Silk.NET.WebGPU.Buffer; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// GPU layer compositing via . +/// +public sealed unsafe partial class WebGPUDrawingBackend +{ + private const string ComposeLayerPipelineKey = "compose-layer"; + private const string ComposeLayerConfigBufferKey = "compose-layer/config"; + + /// + /// Attempts to composite a source layer onto a destination using a GPU compute shader. + /// Returns when GPU compositing is not available — the caller + /// should fall back to for + /// CPU-backed destinations where the transfer overhead outweighs the GPU benefit. + /// + private bool TryComposeLayerGpu( + Configuration configuration, + ICanvasFrame source, + ICanvasFrame destination, + Point destinationOffset, + GraphicsOptions options) + where TPixel : unmanaged, IPixel + { + if (!TryGetCompositeTextureFormat(out WebGPUTextureFormatId formatId, out FeatureName requiredFeature)) + { + return false; + } + + TextureFormat textureFormat = WebGPUTextureFormatMapper.ToSilk(formatId); + + // Only use GPU compositing when the destination is a native surface. + // CPU-backed destinations fall back to DefaultDrawingBackend where a + // simple pixel blend avoids the upload/readback transfer overhead. + if (!destination.TryGetNativeSurface(out NativeSurface? nativeSurface)) + { + return false; + } + + int srcWidth = source.Bounds.Width; + int srcHeight = source.Bounds.Height; + if (srcWidth <= 0 || srcHeight <= 0) + { + return true; // Nothing to composite. + } + + if (!ComposeLayerComputeShader.TryGetCode(textureFormat, out byte[] shaderCode, out _)) + { + return false; + } + + // TryGetCode already validates format support via TryGetInputSampleType internally. + _ = CompositeComputeShader.TryGetInputSampleType(textureFormat, out TextureSampleType inputSampleType); + + // Create a flush context against the destination surface. + WebGPUFlushContext? flushContext = WebGPUFlushContext.Create( + destination, + textureFormat, + requiredFeature, + configuration.MemoryAllocator); + + if (flushContext is null) + { + return false; + } + + try + { + if (!flushContext.EnsureCommandEncoder()) + { + return false; + } + + // Acquire the source texture: bind the existing GPU texture if native, + // otherwise upload from CPU pixels. + if (!TryAcquireSourceTexture( + flushContext, + source, + configuration.MemoryAllocator, + out Texture* sourceTexture, + out TextureView* sourceTextureView)) + { + return false; + } + + // The destination texture/view are guaranteed valid by WebGPUFlushContext.Create. + Texture* destTexture = flushContext.TargetTexture; + TextureView* destTextureView = flushContext.TargetView; + + // Create output texture sized to the destination. + int destWidth = destination.Bounds.Width; + int destHeight = destination.Bounds.Height; + + // Clamp compositing region to both source and destination bounds. + int startX = Math.Max(0, -destinationOffset.X); + int startY = Math.Max(0, -destinationOffset.Y); + int endX = Math.Min(srcWidth, destWidth - destinationOffset.X); + int endY = Math.Min(srcHeight, destHeight - destinationOffset.Y); + + if (endX <= startX || endY <= startY) + { + return true; // No overlap. + } + + int compositeWidth = endX - startX; + int compositeHeight = endY - startY; + + if (!TryCreateCompositionTexture(flushContext, compositeWidth, compositeHeight, out Texture* outputTexture, out TextureView* outputTextureView, out _)) + { + return false; + } + + // Get or create the compute pipeline. + string pipelineKey = $"{ComposeLayerPipelineKey}/{textureFormat}"; + bool LayoutFactory(WebGPU api, Device* device, out BindGroupLayout* layout, out string? layoutError) + => TryCreateComposeLayerBindGroupLayout( + api, + device, + textureFormat, + inputSampleType, + out layout, + out layoutError); + + if (!flushContext.DeviceState.TryGetOrCreateCompositeComputePipeline( + pipelineKey, + shaderCode, + LayoutFactory, + out BindGroupLayout* bindGroupLayout, + out ComputePipeline* pipeline, + out _)) + { + return false; + } + + // Create and upload the config uniform. + nuint configSize = (nuint)Unsafe.SizeOf(); + if (!flushContext.DeviceState.TryGetOrCreateSharedBuffer( + ComposeLayerConfigBufferKey, + BufferUsage.Uniform | BufferUsage.CopyDst, + configSize, + out WgpuBuffer* configBuffer, + out _, + out _)) + { + return false; + } + + LayerConfigGpu config = new() + { + SourceWidth = (uint)srcWidth, + SourceHeight = (uint)srcHeight, + DestOffsetX = destinationOffset.X + startX, + DestOffsetY = destinationOffset.Y + startY, + ColorBlendMode = (uint)options.ColorBlendingMode, + AlphaCompositionMode = (uint)options.AlphaCompositionMode, + BlendPercentage = FloatToUInt32Bits(options.BlendPercentage), + Padding = 0 + }; + + flushContext.Api.QueueWriteBuffer(flushContext.Queue, configBuffer, 0, &config, configSize); + + // Create bind group. + BindGroupEntry* bindGroupEntries = stackalloc BindGroupEntry[4]; + bindGroupEntries[0] = new BindGroupEntry { Binding = 0, TextureView = sourceTextureView }; + bindGroupEntries[1] = new BindGroupEntry { Binding = 1, TextureView = destTextureView }; + bindGroupEntries[2] = new BindGroupEntry { Binding = 2, TextureView = outputTextureView }; + bindGroupEntries[3] = new BindGroupEntry + { + Binding = 3, + Buffer = configBuffer, + Offset = 0, + Size = configSize + }; + + BindGroupDescriptor bindGroupDescriptor = new() + { + Layout = bindGroupLayout, + EntryCount = 4, + Entries = bindGroupEntries + }; + + BindGroup* bindGroup = flushContext.Api.DeviceCreateBindGroup(flushContext.Device, in bindGroupDescriptor); + if (bindGroup is null) + { + return false; + } + + flushContext.TrackBindGroup(bindGroup); + + // Dispatch compute. + uint tileCountX = DivideRoundUp(compositeWidth, CompositeTileWidth); + uint tileCountY = DivideRoundUp(compositeHeight, CompositeTileHeight); + + ComputePassDescriptor passDescriptor = default; + ComputePassEncoder* passEncoder = flushContext.Api.CommandEncoderBeginComputePass(flushContext.CommandEncoder, in passDescriptor); + if (passEncoder is null) + { + return false; + } + + try + { + flushContext.Api.ComputePassEncoderSetPipeline(passEncoder, pipeline); + flushContext.Api.ComputePassEncoderSetBindGroup(passEncoder, 0, bindGroup, 0, null); + flushContext.Api.ComputePassEncoderDispatchWorkgroups(passEncoder, tileCountX, tileCountY, 1); + } + finally + { + flushContext.Api.ComputePassEncoderEnd(passEncoder); + flushContext.Api.ComputePassEncoderRelease(passEncoder); + } + + // Copy output back to destination texture at the compositing offset. + CopyTextureRegion( + flushContext, + outputTexture, + 0, + 0, + destTexture, + destinationOffset.X + startX, + destinationOffset.Y + startY, + compositeWidth, + compositeHeight); + + return TrySubmit(flushContext); + } + finally + { + flushContext.Dispose(); + } + } + + /// + /// Creates the bind-group layout for the layer compositing compute shader. + /// + private static bool TryCreateComposeLayerBindGroupLayout( + WebGPU api, + Device* device, + TextureFormat outputTextureFormat, + TextureSampleType inputTextureSampleType, + out BindGroupLayout* layout, + out string? error) + { + BindGroupLayoutEntry* entries = stackalloc BindGroupLayoutEntry[4]; + + // Binding 0: source layer texture (read). + entries[0] = new BindGroupLayoutEntry + { + Binding = 0, + Visibility = ShaderStage.Compute, + Texture = new TextureBindingLayout + { + SampleType = inputTextureSampleType, + ViewDimension = TextureViewDimension.Dimension2D, + Multisampled = false + } + }; + + // Binding 1: destination/backdrop texture (read). + entries[1] = new BindGroupLayoutEntry + { + Binding = 1, + Visibility = ShaderStage.Compute, + Texture = new TextureBindingLayout + { + SampleType = inputTextureSampleType, + ViewDimension = TextureViewDimension.Dimension2D, + Multisampled = false + } + }; + + // Binding 2: output texture (write storage). + entries[2] = new BindGroupLayoutEntry + { + Binding = 2, + Visibility = ShaderStage.Compute, + StorageTexture = new StorageTextureBindingLayout + { + Access = StorageTextureAccess.WriteOnly, + Format = outputTextureFormat, + ViewDimension = TextureViewDimension.Dimension2D + } + }; + + // Binding 3: uniform config buffer. + entries[3] = new BindGroupLayoutEntry + { + Binding = 3, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Uniform, + HasDynamicOffset = false, + MinBindingSize = (nuint)Unsafe.SizeOf() + } + }; + + BindGroupLayoutDescriptor descriptor = new() + { + EntryCount = 4, + Entries = entries + }; + + layout = api.DeviceCreateBindGroupLayout(device, in descriptor); + if (layout is null) + { + error = "Failed to create compose-layer bind group layout."; + return false; + } + + error = null; + return true; + } + + /// + /// Acquires a GPU texture and view for the source frame. If the source is already + /// a native GPU texture it is bound directly; otherwise CPU pixels are uploaded + /// to a temporary texture. + /// + private static bool TryAcquireSourceTexture( + WebGPUFlushContext flushContext, + ICanvasFrame source, + MemoryAllocator memoryAllocator, + out Texture* sourceTexture, + out TextureView* sourceTextureView) + where TPixel : unmanaged, IPixel + { + if (source.TryGetNativeSurface(out NativeSurface? sourceSurface) + && sourceSurface.TryGetCapability(out WebGPUSurfaceCapability? srcCapability)) + { + sourceTexture = (Texture*)srcCapability.TargetTexture; + sourceTextureView = (TextureView*)srcCapability.TargetTextureView; + return true; + } + + if (source.TryGetCpuRegion(out Buffer2DRegion sourceRegion)) + { + if (!TryCreateCompositionTexture(flushContext, sourceRegion.Width, sourceRegion.Height, out sourceTexture, out sourceTextureView, out _)) + { + return false; + } + + WebGPUFlushContext.UploadTextureFromRegion( + flushContext.Api, + flushContext.Queue, + sourceTexture, + sourceRegion, + memoryAllocator); + return true; + } + + sourceTexture = null; + sourceTextureView = null; + return false; + } + + /// + /// GPU uniform config matching the WGSL LayerConfig struct layout. + /// + [StructLayout(LayoutKind.Sequential)] + private struct LayerConfigGpu + { + public uint SourceWidth; + public uint SourceHeight; + public int DestOffsetX; + public int DestOffsetY; + public uint ColorBlendMode; + public uint AlphaCompositionMode; + public uint BlendPercentage; + public uint Padding; + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs new file mode 100644 index 000000000..030548994 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CompositePixels.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using Silk.NET.WebGPU; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Pixel-format registration for composite session I/O. +/// +/// +/// The map defined by is intentionally explicit and only +/// includes one-to-one format mappings where the GPU texture format can round-trip the pixel payload +/// without channel swizzle or custom conversion logic. +/// Only formats that support storage texture binding (required by the compute compositor) +/// are included. Formats that lack storage support are omitted and fall back to the CPU backend. +/// +public sealed partial class WebGPUDrawingBackend +{ + /// + /// Builds the static registration table that maps implementations to + /// compatible WebGPU storage/sampling formats. + /// + /// The registration map used during flush dispatch. + private static Dictionary CreateCompositePixelHandlers() => + + // Only formats with native or feature-gated storage binding support. + new() + { + [typeof(Byte4)] = CompositePixelRegistration.Create(TextureFormat.Rgba8Uint), + [typeof(NormalizedByte4)] = CompositePixelRegistration.Create(TextureFormat.Rgba8Snorm), + + [typeof(HalfVector4)] = CompositePixelRegistration.Create(TextureFormat.Rgba16float), + + [typeof(Short4)] = CompositePixelRegistration.Create(TextureFormat.Rgba16Sint), + + [typeof(Rgba32)] = CompositePixelRegistration.Create(TextureFormat.Rgba8Unorm), + [typeof(Bgra32)] = CompositePixelRegistration.Create(TextureFormat.Bgra8Unorm, FeatureName.Bgra8UnormStorage), + [typeof(RgbaVector)] = CompositePixelRegistration.Create(TextureFormat.Rgba32float), + + [typeof(Rgba64)] = CompositePixelRegistration.Create(TextureFormat.Rgba16Uint) + }; + + /// + /// Resolves the WebGPU texture format identifier for . + /// + /// The requested pixel type. + /// Receives the mapped texture format identifier on success. + /// + /// when the pixel type has a registered GPU format mapping; otherwise . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TryGetCompositeTextureFormat(out WebGPUTextureFormatId formatId) + where TPixel : unmanaged, IPixel + { + if (!CompositePixelHandlers.TryGetValue(typeof(TPixel), out CompositePixelRegistration registration)) + { + formatId = default; + return false; + } + + formatId = WebGPUTextureFormatMapper.FromSilk(registration.TextureFormat); + return true; + } + + /// + /// Resolves the WebGPU texture format identifier and any required device feature + /// for . + /// + /// The requested pixel type. + /// Receives the mapped texture format identifier on success. + /// + /// Receives the device feature required for storage binding, or + /// when no special feature is needed. + /// + /// + /// when the pixel type has a registered GPU format mapping; otherwise . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TryGetCompositeTextureFormat(out WebGPUTextureFormatId formatId, out FeatureName requiredFeature) + where TPixel : unmanaged, IPixel + { + if (!CompositePixelHandlers.TryGetValue(typeof(TPixel), out CompositePixelRegistration registration)) + { + formatId = default; + requiredFeature = FeatureName.Undefined; + return false; + } + + formatId = WebGPUTextureFormatMapper.FromSilk(registration.TextureFormat); + requiredFeature = registration.RequiredFeature; + return true; + } + + /// + /// Per-pixel registration payload consumed by GPU composition setup. + /// + private readonly struct CompositePixelRegistration + { + /// + /// Initializes a new instance of the struct. + /// + /// The registered pixel CLR type. + /// The matching WebGPU texture format. + /// The unmanaged pixel size in bytes. + /// Optional device feature required for storage binding support. + public CompositePixelRegistration( + Type pixelType, + TextureFormat textureFormat, + int pixelSizeInBytes, + FeatureName requiredFeature) + { + this.PixelType = pixelType; + this.TextureFormat = textureFormat; + this.PixelSizeInBytes = pixelSizeInBytes; + this.RequiredFeature = requiredFeature; + } + + /// + /// Gets the CLR pixel type registered for this mapping. + /// + public Type PixelType { get; } + + /// + /// Gets the WebGPU texture format used for this pixel type. + /// + public TextureFormat TextureFormat { get; } + + /// + /// Gets the unmanaged size of the pixel type in bytes. + /// + public int PixelSizeInBytes { get; } + + /// + /// Gets the optional device feature required for storage binding support. + /// means the format is natively storable. + /// + public FeatureName RequiredFeature { get; } + + /// + /// Creates a registration record for with native storage support. + /// + /// The matching WebGPU texture format. + /// The initialized registration. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static CompositePixelRegistration Create(TextureFormat textureFormat) + where TPixel : unmanaged, IPixel + => new(typeof(TPixel), textureFormat, Unsafe.SizeOf(), FeatureName.Undefined); + + /// + /// Creates a registration record for with a required device feature. + /// + /// The matching WebGPU texture format. + /// The device feature required for storage binding. + /// The initialized registration. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static CompositePixelRegistration Create(TextureFormat textureFormat, FeatureName requiredFeature) + where TPixel : unmanaged, IPixel + => new(typeof(TPixel), textureFormat, Unsafe.SizeOf(), requiredFeature); + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CoverageRasterizer.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CoverageRasterizer.cs new file mode 100644 index 000000000..a0ae5654f --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CoverageRasterizer.cs @@ -0,0 +1,1328 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.InteropServices; +using Silk.NET.WebGPU; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using WgpuBuffer = Silk.NET.WebGPU.Buffer; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Coverage rasterization helpers. +/// +public sealed unsafe partial class WebGPUDrawingBackend +{ + private const int TileHeight = 16; + private const int EdgeStrideBytes = 28; + private const int FixedShift = 8; + private const int FixedOne = 1 << FixedShift; + private const int CsrWorkgroupSize = 256; + + private IMemoryOwner? cachedCoverageLineUpload; + private int cachedCoverageLineLength; + + /// + /// Writes bind-group entries and returns the number of populated entries. + /// + private delegate uint BindGroupEntryWriter(Span entries); + + /// + /// Encapsulates dispatch logic for a compute pass. + /// + private delegate void ComputePassDispatch(ComputePassEncoder* pass); + + /// + /// Computes the maximum number of outline edges a single centerline edge can produce + /// for the given stroke parameters. The worst case is a round join or cap where + /// the arc step count scales with stroke width: max(4, ceil(π * halfWidth * 0.5)). + /// + private static int ComputeOutlineEdgesPerCenterline(float halfWidth, LineJoin lineJoin, LineCap lineCap) + { + // Side edges always produce exactly 2. + // Join: 1 inner bevel + outer edges (miter variants: up to 3, bevel: 1, round: arc steps). + // Cap: butt=1, square=3, round=arc steps. + int roundArcSteps = Math.Max(4, (int)MathF.Ceiling(MathF.PI * halfWidth * 0.5f)); + int maxJoin = lineJoin is LineJoin.Round or LineJoin.MiterRound + ? 1 + roundArcSteps + : 4; // miter clamp worst case: 1 inner + 3 outer + int maxCap = lineCap == LineCap.Round + ? roundArcSteps + : 3; // square cap + return Math.Max(Math.Max(maxJoin, maxCap), 2); + } + + private bool TryCreateEdgeBuffer( + WebGPUFlushContext flushContext, + List definitions, + Configuration configuration, + out WgpuBuffer* edgeBuffer, + out nuint edgeBufferSize, + out IMemoryOwner edgePlacements, + out int totalEdgeCount, + out int totalBandOffsetEntries, + out WgpuBuffer* bandOffsetsBuffer, + out nuint bandOffsetsBufferSize, + out StrokeExpandInfo strokeExpandInfo, + out string? error) + where TPixel : unmanaged, IPixel + { + edgeBuffer = null; + edgeBufferSize = 0; + edgePlacements = null!; + totalEdgeCount = 0; + totalBandOffsetEntries = 0; + bandOffsetsBuffer = null; + bandOffsetsBufferSize = 0; + strokeExpandInfo = default; + error = null; + if (definitions.Count == 0) + { + return true; + } + + edgePlacements = flushContext.MemoryAllocator.Allocate(definitions.Count); + Span edgePlacementsSpan = edgePlacements.Memory.Span; + int runningEdgeStart = 0; + int runningBandOffset = 0; + + // Build pre-split geometry for each definition and compute edge placements. + DefinitionGeometry[] geometries = new DefinitionGeometry[definitions.Count]; + + // Track stroke definitions for the expand dispatch. + List? strokeCommands = null; + int totalStrokeCenterlineEdges = 0; + int totalOutlineSlots = 0; + + for (int i = 0; i < definitions.Count; i++) + { + CompositionCoverageDefinition definition = definitions[i]; + Rectangle interest = definition.RasterizerOptions.Interest; + if (interest.Width <= 0 || interest.Height <= 0) + { + error = "Invalid coverage bounds."; + return false; + } + + uint fillRule = definition.RasterizerOptions.IntersectionRule == IntersectionRule.EvenOdd ? 1u : 0u; + + int bandCount = (int)DivideRoundUp(interest.Height, TileHeight); + + IMemoryOwner? defEdgeOwner; + int edgeCount; + IMemoryOwner? defBandOffsets; + bool edgeSuccess; + if (definition.IsStroke) + { + edgeSuccess = TryBuildStrokeEdges( + flushContext.MemoryAllocator, + definition.Path, + in interest, + definition.RasterizerOptions.SamplingOrigin, + definition.StrokeWidth, + (float)(definition.StrokeOptions?.MiterLimit ?? 4.0), + bandCount, + out defEdgeOwner, + out edgeCount, + out defBandOffsets, + out error); + } + else + { + edgeSuccess = TryBuildFixedPointEdges( + flushContext.MemoryAllocator, + definition.Path, + in interest, + definition.RasterizerOptions.SamplingOrigin, + out defEdgeOwner, + out edgeCount, + out defBandOffsets, + out error); + } + + if (!edgeSuccess) + { + for (int j = 0; j < i; j++) + { + geometries[j].EdgeOwner?.Dispose(); + } + + return false; + } + + geometries[i] = new DefinitionGeometry(defEdgeOwner, edgeCount, bandCount, defBandOffsets); + + if (definition.IsStroke && edgeCount > 0) + { + // Centerline edges are band-sorted. Create one StrokeExpandCommand per band + // so the GPU expand shader writes outline edges into per-band output slots. + // This produces band-sorted outline edges compatible with the fill rasterizer. + Span clBandOffsets = defBandOffsets!.Memory.Span; + LineCap defLineCap = definition.StrokeOptions?.LineCap ?? LineCap.Butt; + LineJoin defLineJoin = definition.StrokeOptions?.LineJoin ?? LineJoin.Bevel; + int outlineEdgesPerCenterline = ComputeOutlineEdgesPerCenterline( + definition.StrokeWidth * 0.5f, defLineJoin, defLineCap); + strokeCommands ??= []; + for (int b = 0; b < bandCount; b++) + { + uint bandStart = clBandOffsets[b]; + uint bandEnd = b + 1 < clBandOffsets.Length ? clBandOffsets[b + 1] : (uint)edgeCount; + uint bandEdgeCount = bandEnd - bandStart; + if (bandEdgeCount == 0) + { + continue; + } + + int bandOutlineMax = (int)bandEdgeCount * outlineEdgesPerCenterline; + strokeCommands.Add(new StrokeExpandCommand( + i, + (uint)runningEdgeStart + bandStart, + bandEdgeCount, + definition.StrokeWidth * 0.5f, + (uint)defLineCap, + (uint)defLineJoin, + (float)(definition.StrokeOptions?.MiterLimit ?? 4.0), + bandOutlineMax, + Band: b)); + + totalOutlineSlots += bandOutlineMax; + } + + totalStrokeCenterlineEdges += edgeCount; + + // Placeholder EdgePlacement — will be updated after outline space is allocated. + int bandOffsetEntriesForDef = bandCount + 1; + edgePlacementsSpan[i] = new EdgePlacement( + (uint)runningEdgeStart, + (uint)edgeCount, + fillRule, + (uint)runningBandOffset, + (uint)bandCount); + + runningEdgeStart += edgeCount; + runningBandOffset += bandOffsetEntriesForDef; + } + else + { + int bandOffsetEntriesForDef = bandCount + 1; + + edgePlacementsSpan[i] = new EdgePlacement( + (uint)runningEdgeStart, + (uint)edgeCount, + fillRule, + (uint)runningBandOffset, + (uint)bandCount); + + runningEdgeStart += edgeCount; + runningBandOffset += bandOffsetEntriesForDef; + } + } + + totalEdgeCount = runningEdgeStart; + totalBandOffsetEntries = runningBandOffset; + + // Reserve outline edge space for stroke definitions. + // Outline edges are placed after all centerline/fill edges in the buffer. + // Each band within a stroke definition gets its own output slot so the + // resulting outline edges are band-sorted — compatible with the fill rasterizer. + // outlineBandOffsetsPerDef[defIndex] stores the outline band offsets array for + // stroke definitions (null for fills). Used in the merged band offsets upload. + IMemoryOwner?[] outlineBandOffsetsPerDef = new IMemoryOwner?[definitions.Count]; + int outlineRegionStart = totalEdgeCount; + if (strokeCommands is not null) + { + // Assign per-command output offsets and build per-definition outline band offsets. + int runningOutlineOffset = outlineRegionStart; + for (int sc = 0; sc < strokeCommands.Count; sc++) + { + StrokeExpandCommand cmd = strokeCommands[sc]; + strokeCommands[sc] = cmd with + { + OutputStart = (uint)runningOutlineOffset, + OutputMax = (uint)(runningOutlineOffset + cmd.OutlineMax) + }; + runningOutlineOffset += cmd.OutlineMax; + } + + totalEdgeCount = runningOutlineOffset; + + // Build outline band offsets for each stroke definition. + // Commands are ordered by definition, then by band within each definition. + int cmdCursor = 0; + while (cmdCursor < strokeCommands.Count) + { + int defIndex = strokeCommands[cmdCursor].DefinitionIndex; + int defBandCount = geometries[defIndex].BandCount; + int defOutlineStart = (int)strokeCommands[cmdCursor].OutputStart; + + // Build full band offsets: offsets[b] = local offset within this def's outline region. + IMemoryOwner outOffsetsOwner = flushContext.MemoryAllocator.Allocate(defBandCount + 1); + Span outOffsets = outOffsetsOwner.Memory.Span; + uint localOffset = 0; + for (int b = 0; b < defBandCount; b++) + { + outOffsets[b] = localOffset; + if (cmdCursor < strokeCommands.Count + && strokeCommands[cmdCursor].DefinitionIndex == defIndex + && strokeCommands[cmdCursor].Band == b) + { + localOffset += (uint)strokeCommands[cmdCursor].OutlineMax; + cmdCursor++; + } + } + + int defOutlineTotal = (int)localOffset; + outOffsets[defBandCount] = (uint)defOutlineTotal; + outlineBandOffsetsPerDef[defIndex] = outOffsetsOwner; + + // Update EdgePlacement to point to the outline region. + EdgePlacement oldPlacement = edgePlacementsSpan[defIndex]; + edgePlacementsSpan[defIndex] = new EdgePlacement( + (uint)defOutlineStart, + (uint)defOutlineTotal, + oldPlacement.FillRule, + (uint)runningBandOffset, + (uint)defBandCount); + + runningBandOffset += defBandCount + 1; + totalBandOffsetEntries = runningBandOffset; + } + } + + if (totalEdgeCount == 0) + { + edgeBufferSize = EdgeStrideBytes; + int emptyOffsetsCount = Math.Max(totalBandOffsetEntries, 1); + bandOffsetsBufferSize = checked((nuint)(emptyOffsetsCount * sizeof(uint))); + if (!TryGetOrCreateCoverageBuffer(flushContext, "coverage-aggregated-edges", BufferUsage.Storage | BufferUsage.CopyDst, edgeBufferSize, out edgeBuffer, out error) || + !TryGetOrCreateCoverageBuffer(flushContext, "band-offsets", BufferUsage.Storage | BufferUsage.CopyDst, bandOffsetsBufferSize, out bandOffsetsBuffer, out error)) + { + return false; + } + + flushContext.Api.CommandEncoderClearBuffer(flushContext.CommandEncoder, bandOffsetsBuffer, 0, bandOffsetsBufferSize); + return true; + } + + // Merge edge arrays. Includes space for outline edges at the end. + int edgeBufferBytes = checked(totalEdgeCount * EdgeStrideBytes); + edgeBufferSize = (nuint)edgeBufferBytes; + + // Upload only the centerline/fill edges; outline region will be written by GPU. + int uploadEdgeCount = outlineRegionStart; + + IMemoryOwner? mergedEdgeOwner; + if (geometries.Length == 1 && geometries[0].EdgeOwner is not null && strokeCommands is null) + { + mergedEdgeOwner = geometries[0].EdgeOwner!; + } + else + { + mergedEdgeOwner = flushContext.MemoryAllocator.Allocate(uploadEdgeCount > 0 ? uploadEdgeCount : 1); + int mergedEdgeIndex = 0; + for (int defIndex = 0; defIndex < geometries.Length; defIndex++) + { + ref DefinitionGeometry geometry = ref geometries[defIndex]; + if (geometry.EdgeCount == 0 || geometry.EdgeOwner is null) + { + continue; + } + + ReadOnlySpan source = geometry.EdgeOwner.Memory.Span; + Span dest = mergedEdgeOwner.Memory.Span.Slice(mergedEdgeIndex, geometry.EdgeCount); + source.CopyTo(dest); + mergedEdgeIndex += geometry.EdgeCount; + } + } + + if (!TryGetOrCreateCoverageBuffer( + flushContext, + "coverage-aggregated-edges", + BufferUsage.Storage | BufferUsage.CopyDst, + (nuint)edgeBufferBytes, + out edgeBuffer, + out error)) + { + DisposeGeometries(geometries, mergedEdgeOwner, outlineBandOffsetsPerDef); + return false; + } + + // Upload centerline/fill edges to the beginning of the buffer. + if (uploadEdgeCount > 0) + { + Span edgeUpload = MemoryMarshal.AsBytes(mergedEdgeOwner.Memory.Span); + if (!this.TryUploadDirtyCoverageRange( + flushContext, + edgeBuffer, + edgeUpload, + ref this.cachedCoverageLineUpload, + ref this.cachedCoverageLineLength, + out error)) + { + DisposeGeometries(geometries, mergedEdgeOwner); + return false; + } + } + + // Clear the outline region so unused slots have y0 == y1 == 0 (no winding contribution). + if (totalOutlineSlots > 0) + { + nuint outlineByteOffset = (nuint)(outlineRegionStart * EdgeStrideBytes); + nuint outlineByteSize = (nuint)(totalOutlineSlots * EdgeStrideBytes); + flushContext.Api.CommandEncoderClearBuffer(flushContext.CommandEncoder, edgeBuffer, outlineByteOffset, outlineByteSize); + } + + // Build merged band offsets from pre-computed per-definition data. + int bandOffsetsCount = Math.Max(totalBandOffsetEntries, 1); + bandOffsetsBufferSize = checked((nuint)(bandOffsetsCount * sizeof(uint))); + + if (!TryGetOrCreateCoverageBuffer(flushContext, "band-offsets", BufferUsage.Storage | BufferUsage.CopyDst, bandOffsetsBufferSize, out bandOffsetsBuffer, out error)) + { + DisposeGeometries(geometries, mergedEdgeOwner, outlineBandOffsetsPerDef); + return false; + } + + if (totalEdgeCount > 0 && totalBandOffsetEntries > 0) + { + using IMemoryOwner mergedOffsetsOwner = flushContext.MemoryAllocator.Allocate(totalBandOffsetEntries, AllocationOptions.Clean); + Span mergedOffsets = mergedOffsetsOwner.Memory.Span; + for (int defIndex = 0; defIndex < geometries.Length; defIndex++) + { + ref DefinitionGeometry geometry = ref geometries[defIndex]; + EdgePlacement placement = edgePlacementsSpan[defIndex]; + int bandStart = (int)placement.CsrOffsetsStart; + + // Use outline band offsets for stroke definitions, fill band offsets otherwise. + IMemoryOwner? defOffsetsOwner = outlineBandOffsetsPerDef[defIndex] ?? geometry.BandOffsets; + if (defOffsetsOwner is not null) + { + ReadOnlySpan defOffsets = defOffsetsOwner.Memory.Span; + for (int b = 0; b < defOffsets.Length; b++) + { + mergedOffsets[bandStart + b] = defOffsets[b]; + } + } + } + + fixed (uint* offsetsPtr = &MemoryMarshal.GetReference(mergedOffsets)) + { + flushContext.Api.QueueWriteBuffer( + flushContext.Queue, + bandOffsetsBuffer, + 0, + offsetsPtr, + (nuint)(totalBandOffsetEntries * sizeof(uint))); + } + } + else + { + flushContext.Api.CommandEncoderClearBuffer(flushContext.CommandEncoder, bandOffsetsBuffer, 0, bandOffsetsBufferSize); + } + + // Build stroke expand info for the GPU dispatch. + if (strokeCommands is not null && strokeCommands.Count > 0) + { + strokeExpandInfo = new StrokeExpandInfo(strokeCommands, totalStrokeCenterlineEdges); + } + + DisposeGeometries(geometries, mergedEdgeOwner, outlineBandOffsetsPerDef); + + error = null; + return true; + } + + /// + /// Gets or creates a shared GPU buffer used by the coverage rasterization pipeline. + /// + private static bool TryGetOrCreateCoverageBuffer( + WebGPUFlushContext flushContext, + string bufferKey, + BufferUsage usage, + nuint requiredSize, + out WgpuBuffer* buffer, + out string? error) + => flushContext.DeviceState.TryGetOrCreateSharedBuffer( + bufferKey, + usage, + requiredSize, + out buffer, + out _, + out error); + + /// + /// Uploads only the changed byte range of a coverage buffer payload. + /// + private bool TryUploadDirtyCoverageRange( + WebGPUFlushContext flushContext, + WgpuBuffer* destinationBuffer, + ReadOnlySpan source, + ref IMemoryOwner? cachedOwner, + ref int cachedLength, + out string? error) + { + error = null; + if (source.Length == 0) + { + cachedLength = 0; + return true; + } + + if (cachedOwner is null || cachedOwner.Memory.Length < source.Length) + { + cachedOwner?.Dispose(); + cachedOwner = flushContext.MemoryAllocator.Allocate(source.Length); + cachedLength = 0; + } + + Span cached = cachedOwner.Memory.Span[..source.Length]; + int previousLength = cachedLength; + int commonLength = Math.Min(previousLength, source.Length); + int commonAlignedLength = commonLength & ~0x3; + ReadOnlySpan sourceWords = MemoryMarshal.Cast(source[..commonAlignedLength]); + ReadOnlySpan cachedWords = MemoryMarshal.Cast(cached[..commonAlignedLength]); + + int firstDifferentWord = 0; + while (firstDifferentWord < sourceWords.Length && cachedWords[firstDifferentWord] == sourceWords[firstDifferentWord]) + { + firstDifferentWord++; + } + + int firstDifferent = firstDifferentWord * sizeof(uint); + while (firstDifferent < commonLength && cached[firstDifferent] == source[firstDifferent]) + { + firstDifferent++; + } + + if (firstDifferent >= source.Length) + { + // Data is identical to cache — skip upload and copy. + return true; + } + + int lastDifferent = source.Length - 1; + if (lastDifferent < commonLength) + { + while (lastDifferent >= firstDifferent && + lastDifferent >= commonAlignedLength && + cached[lastDifferent] == source[lastDifferent]) + { + lastDifferent--; + } + + int firstWordIndex = firstDifferent / sizeof(uint); + int lastWordIndex = Math.Min(lastDifferent / sizeof(uint), sourceWords.Length - 1); + while (lastWordIndex >= firstWordIndex && cachedWords[lastWordIndex] == sourceWords[lastWordIndex]) + { + lastWordIndex--; + } + + if (lastWordIndex >= firstWordIndex) + { + lastDifferent = Math.Min(lastDifferent, (lastWordIndex * sizeof(uint)) + (sizeof(uint) - 1)); + } + } + + int uploadOffset = firstDifferent & ~0x3; + int uploadEnd = firstDifferent + (lastDifferent - firstDifferent) + 1; + uploadEnd = (uploadEnd + 3) & ~0x3; + uploadEnd = Math.Min(uploadEnd, source.Length); + int uploadLength = uploadEnd - uploadOffset; + + fixed (byte* sourcePtr = source) + { + flushContext.Api.QueueWriteBuffer( + flushContext.Queue, + destinationBuffer, + (nuint)uploadOffset, + sourcePtr + uploadOffset, + (nuint)uploadLength); + } + + source.CopyTo(cached); + cachedLength = source.Length; + return true; + } + + /// + /// Disposes all edge memory owners from geometry entries and the merged owner. + /// + private static void DisposeGeometries( + DefinitionGeometry[] geometries, + IMemoryOwner? mergedEdgeOwner, + IMemoryOwner?[]? outlineBandOffsets = null) + { + for (int i = 0; i < geometries.Length; i++) + { + // For single-definition, EdgeOwner == mergedEdgeOwner; only dispose once. + if (geometries[i].EdgeOwner is not null && geometries[i].EdgeOwner != mergedEdgeOwner) + { + geometries[i].EdgeOwner!.Dispose(); + } + + geometries[i].EdgeOwner = null; + geometries[i].BandOffsets?.Dispose(); + geometries[i].BandOffsets = null; + } + + mergedEdgeOwner?.Dispose(); + + if (outlineBandOffsets is not null) + { + for (int i = 0; i < outlineBandOffsets.Length; i++) + { + outlineBandOffsets[i]?.Dispose(); + outlineBandOffsets[i] = null; + } + } + } + + private void DisposeCoverageResources() + { + this.cachedCoverageLineUpload?.Dispose(); + this.cachedCoverageLineUpload = null; + this.cachedCoverageLineLength = 0; + } + + /// + /// Flattens a path into fixed-point (24.8) edges placed into per-band regions. + /// Edges spanning multiple bands are duplicated (not clipped) so each band's + /// range is contiguous. Band offsets provide direct indexing into the edge buffer. + /// The shader handles per-tile Y clipping via clip_vertical. + /// + /// + /// Uses a two-pass approach over the flattened path to avoid an intermediate buffer: + /// pass 1 counts edges per band, pass 2 scatters directly into the final buffer. + /// + private static bool TryBuildFixedPointEdges( + MemoryAllocator allocator, + IPath path, + in Rectangle interest, + RasterizerSamplingOrigin samplingOrigin, + out IMemoryOwner? edgeOwner, + out int edgeCount, + out IMemoryOwner? bandOffsets, + out string? error) + { + error = null; + edgeOwner = null; + edgeCount = 0; + bandOffsets = null; + bool samplePixelCenter = samplingOrigin == RasterizerSamplingOrigin.PixelCenter; + float samplingOffsetX = samplePixelCenter ? 0.5F : 0F; + float samplingOffsetY = samplePixelCenter ? 0.5F : 0F; + int height = interest.Height; + int interestX = interest.X; + int interestY = interest.Y; + int bandCount = (int)DivideRoundUp(height, TileHeight); + + // Flatten once and reuse the list for both passes. + IEnumerable flattened = path.Flatten(); + IReadOnlyList contours = flattened is IReadOnlyList list + ? list + : [.. flattened]; + + // Pass 1: Count edges per band. + int totalSubEdges = 0; + using IMemoryOwner bandCountsOwner = allocator.Allocate(bandCount, AllocationOptions.Clean); + Span bandCounts = bandCountsOwner.Memory.Span; + + for (int c = 0; c < contours.Count; c++) + { + ISimplePath simplePath = contours[c]; + ReadOnlySpan points = simplePath.Points.Span; + int segmentCount = simplePath.IsClosed ? points.Length : points.Length - 1; + + for (int j = 0; j < segmentCount; j++) + { + PointF p0 = points[j]; + PointF p1 = points[j + 1 == points.Length ? 0 : j + 1]; + + int y0 = (int)MathF.Round(((p0.Y - interestY) + samplingOffsetY) * FixedOne); + int y1 = (int)MathF.Round(((p1.Y - interestY) + samplingOffsetY) * FixedOne); + if (y0 == y1) + { + continue; + } + + int yMinFixed = Math.Min(y0, y1); + int yMaxFixed = Math.Max(y0, y1); + int minRow = Math.Max(0, yMinFixed >> FixedShift); + int maxRow = Math.Min(height - 1, (yMaxFixed - 1) >> FixedShift); + + if (minRow > maxRow) + { + continue; + } + + int minBand = minRow / TileHeight; + int maxBand = maxRow / TileHeight; + for (int b = minBand; b <= maxBand; b++) + { + bandCounts[b]++; + } + + totalSubEdges += maxBand - minBand + 1; + } + } + + if (totalSubEdges == 0) + { + return true; + } + + // Prefix sum → band offsets. + IMemoryOwner offsetsOwner = allocator.Allocate(bandCount + 1); + Span offsets = offsetsOwner.Memory.Span; + uint running = 0; + for (int b = 0; b < bandCount; b++) + { + offsets[b] = running; + running += (uint)bandCounts[b]; + } + + offsets[bandCount] = running; + + // Pass 2: Scatter edges directly into the final buffer. + IMemoryOwner finalOwner = allocator.Allocate(totalSubEdges); + Span finalSpan = finalOwner.Memory.Span; + using IMemoryOwner writeCursorsOwner = allocator.Allocate(bandCount, AllocationOptions.Clean); + Span writeCursors = writeCursorsOwner.Memory.Span; + + for (int c = 0; c < contours.Count; c++) + { + ISimplePath simplePath = contours[c]; + ReadOnlySpan points = simplePath.Points.Span; + int segmentCount = simplePath.IsClosed ? points.Length : points.Length - 1; + for (int j = 0; j < segmentCount; j++) + { + PointF p0 = points[j]; + PointF p1 = points[j + 1 == points.Length ? 0 : j + 1]; + float fx0 = (p0.X - interestX) + samplingOffsetX; + float fy0 = (p0.Y - interestY) + samplingOffsetY; + float fx1 = (p1.X - interestX) + samplingOffsetX; + float fy1 = (p1.Y - interestY) + samplingOffsetY; + + int x0 = (int)MathF.Round(fx0 * FixedOne); + int y0 = (int)MathF.Round(fy0 * FixedOne); + int x1 = (int)MathF.Round(fx1 * FixedOne); + int y1 = (int)MathF.Round(fy1 * FixedOne); + if (y0 == y1) + { + continue; + } + + int yMinFixed = Math.Min(y0, y1); + int yMaxFixed = Math.Max(y0, y1); + int minRow = Math.Max(0, yMinFixed >> FixedShift); + int maxRow = Math.Min(height - 1, (yMaxFixed - 1) >> FixedShift); + + if (minRow > maxRow) + { + continue; + } + + GpuEdge edge = new() { X0 = x0, Y0 = y0, X1 = x1, Y1 = y1 }; + int minBand = minRow / TileHeight; + int maxBand = maxRow / TileHeight; + for (int band = minBand; band <= maxBand; band++) + { + finalSpan[(int)(offsets[band] + writeCursors[band])] = edge; + writeCursors[band]++; + } + } + } + + edgeOwner = finalOwner; + edgeCount = totalSubEdges; + bandOffsets = offsetsOwner; + return true; + } + + /// + /// Builds stroke centerline edges with join and cap descriptors for GPU-side outline generation. + /// The GPU shader expands centerline edges into outline polygon edges and rasterizes them + /// using the same fill rasterizer. Edges are band-sorted with expanded Y ranges to account + /// for stroke offset so each tile only processes edges relevant to its vertical range. + /// + private static bool TryBuildStrokeEdges( + MemoryAllocator allocator, + IPath path, + in Rectangle interest, + RasterizerSamplingOrigin samplingOrigin, + float strokeWidth, + float miterLimit, + int bandCount, + out IMemoryOwner? edgeOwner, + out int edgeCount, + out IMemoryOwner? bandOffsets, + out string? error) + { + error = null; + edgeOwner = null; + edgeCount = 0; + bandOffsets = null; + bool samplePixelCenter = samplingOrigin == RasterizerSamplingOrigin.PixelCenter; + float samplingOffsetX = samplePixelCenter ? 0.5F : 0F; + float samplingOffsetY = samplePixelCenter ? 0.5F : 0F; + int interestX = interest.X; + int interestY = interest.Y; + int height = interest.Height; + + // Maximum Y expansion in pixels: miter joins can extend up to miterLimit * halfWidth. + float halfWidth = strokeWidth * 0.5f; + int yExpansionFixed = (int)MathF.Ceiling(Math.Max(miterLimit, 1f) * halfWidth * FixedOne); + + // Flatten once and reuse the list. + IEnumerable flattened = path.Flatten(); + IReadOnlyList contours = flattened is IReadOnlyList list + ? list + : [.. flattened]; + + // Pass 1: Collect all stroke edges and count per band. + List strokeEdges = []; + List<(int YMinFixed, int YMaxFixed)> edgeYRanges = []; + + for (int c = 0; c < contours.Count; c++) + { + ISimplePath simplePath = contours[c]; + ReadOnlySpan points = simplePath.Points.Span; + + bool isClosed = simplePath.IsClosed; + int segmentCount = isClosed ? points.Length : points.Length - 1; + if (segmentCount == 0) + { + continue; + } + + // Emit centerline edges. + for (int j = 0; j < segmentCount; j++) + { + int j1 = j + 1 == points.Length ? 0 : j + 1; + PointF p0 = points[j]; + PointF p1 = points[j1]; + + float fy0 = (p0.Y - interestY) + samplingOffsetY; + float fy1 = (p1.Y - interestY) + samplingOffsetY; + int iy0 = (int)MathF.Round(fy0 * FixedOne); + int iy1 = (int)MathF.Round(fy1 * FixedOne); + + strokeEdges.Add(new GpuEdge + { + X0 = (int)MathF.Round(((p0.X - interestX) + samplingOffsetX) * FixedOne), + Y0 = iy0, + X1 = (int)MathF.Round(((p1.X - interestX) + samplingOffsetX) * FixedOne), + Y1 = iy1, + }); + + int eMin = Math.Min(iy0, iy1) - yExpansionFixed; + int eMax = Math.Max(iy0, iy1) + yExpansionFixed; + edgeYRanges.Add((eMin, eMax)); + } + + // Emit join descriptors at interior vertices. + int startVertex = isClosed ? 0 : 1; + int endVertex = isClosed ? points.Length : points.Length - 1; + + for (int i = startVertex; i < endVertex; i++) + { + int prev = isClosed ? ((i - 1 + points.Length) % points.Length) : i - 1; + int next = isClosed ? ((i + 1) % points.Length) : i + 1; + + PointF v = points[i]; + PointF pv = points[prev]; + PointF nv = points[next]; + + int vy = (int)MathF.Round(((v.Y - interestY) + samplingOffsetY) * FixedOne); + + strokeEdges.Add(new GpuEdge + { + X0 = (int)MathF.Round(((v.X - interestX) + samplingOffsetX) * FixedOne), + Y0 = vy, + X1 = (int)MathF.Round(((pv.X - interestX) + samplingOffsetX) * FixedOne), + Y1 = (int)MathF.Round(((pv.Y - interestY) + samplingOffsetY) * FixedOne), + Flags = StrokeEdgeFlags.Join, + AdjX = (int)MathF.Round(((nv.X - interestX) + samplingOffsetX) * FixedOne), + AdjY = (int)MathF.Round(((nv.Y - interestY) + samplingOffsetY) * FixedOne), + }); + + edgeYRanges.Add((vy - yExpansionFixed, vy + yExpansionFixed)); + } + + // Emit cap descriptors at open endpoints. + if (!isClosed) + { + PointF capStart = points[0]; + PointF adjStart = points[1]; + int csy = (int)MathF.Round(((capStart.Y - interestY) + samplingOffsetY) * FixedOne); + + strokeEdges.Add(new GpuEdge + { + X0 = (int)MathF.Round(((capStart.X - interestX) + samplingOffsetX) * FixedOne), + Y0 = csy, + X1 = (int)MathF.Round(((adjStart.X - interestX) + samplingOffsetX) * FixedOne), + Y1 = (int)MathF.Round(((adjStart.Y - interestY) + samplingOffsetY) * FixedOne), + Flags = StrokeEdgeFlags.CapStart, + }); + + edgeYRanges.Add((csy - yExpansionFixed, csy + yExpansionFixed)); + + PointF capEnd = points[^1]; + PointF adjEnd = points[^2]; + int cey = (int)MathF.Round(((capEnd.Y - interestY) + samplingOffsetY) * FixedOne); + + strokeEdges.Add(new GpuEdge + { + X0 = (int)MathF.Round(((capEnd.X - interestX) + samplingOffsetX) * FixedOne), + Y0 = cey, + X1 = (int)MathF.Round(((adjEnd.X - interestX) + samplingOffsetX) * FixedOne), + Y1 = (int)MathF.Round(((adjEnd.Y - interestY) + samplingOffsetY) * FixedOne), + Flags = StrokeEdgeFlags.CapEnd, + }); + + edgeYRanges.Add((cey - yExpansionFixed, cey + yExpansionFixed)); + } + } + + if (strokeEdges.Count == 0) + { + return true; + } + + // Band-sort centerline edges using expanded Y ranges so each band contains + // all centerline edges whose outline could affect that band's vertical range. + // This mirrors TryBuildFixedPointEdges but uses pre-computed Y expansion. + using IMemoryOwner bandCountsOwner = allocator.Allocate(bandCount, AllocationOptions.Clean); + Span bandCounts = bandCountsOwner.Memory.Span; + int totalBandEdges = 0; + + for (int i = 0; i < strokeEdges.Count; i++) + { + (int yMin, int yMax) = edgeYRanges[i]; + int minRow = Math.Max(0, yMin >> FixedShift); + int maxRow = Math.Min(height - 1, Math.Max(0, (yMax - 1) >> FixedShift)); + int minBand = Math.Min(minRow / TileHeight, bandCount - 1); + int maxBand = Math.Min(maxRow / TileHeight, bandCount - 1); + for (int b = minBand; b <= maxBand; b++) + { + bandCounts[b]++; + } + + totalBandEdges += maxBand - minBand + 1; + } + + if (totalBandEdges == 0) + { + return true; + } + + // Prefix sum → band offsets. + IMemoryOwner offsetsOwner = allocator.Allocate(bandCount + 1); + Span offsets = offsetsOwner.Memory.Span; + uint running = 0; + for (int b = 0; b < bandCount; b++) + { + offsets[b] = running; + running += (uint)bandCounts[b]; + } + + offsets[bandCount] = running; + + // Scatter centerline edges into band-sorted buffer. + IMemoryOwner finalOwner = allocator.Allocate(totalBandEdges); + Span finalSpan = finalOwner.Memory.Span; + using IMemoryOwner writeCursorsOwner = allocator.Allocate(bandCount, AllocationOptions.Clean); + Span writeCursors = writeCursorsOwner.Memory.Span; + + for (int i = 0; i < strokeEdges.Count; i++) + { + GpuEdge edge = strokeEdges[i]; + (int yMin, int yMax) = edgeYRanges[i]; + int minRow = Math.Max(0, yMin >> FixedShift); + int maxRow = Math.Min(height - 1, Math.Max(0, (yMax - 1) >> FixedShift)); + int minBand = Math.Min(minRow / TileHeight, bandCount - 1); + int maxBand = Math.Min(maxRow / TileHeight, bandCount - 1); + for (int band = minBand; band <= maxBand; band++) + { + finalSpan[(int)(offsets[band] + writeCursors[band])] = edge; + writeCursors[band]++; + } + } + + edgeOwner = finalOwner; + edgeCount = totalBandEdges; + bandOffsets = offsetsOwner; + return true; + } + + /// + /// Creates and executes a compute pass for a coverage pipeline stage. + /// + private bool DispatchComputePass( + WebGPUFlushContext flushContext, + string pipelineKey, + ReadOnlySpan shaderCode, + WebGPUCompositeBindGroupLayoutFactory bindGroupLayoutFactory, + BindGroupEntryWriter entryWriter, + ComputePassDispatch dispatch, + out string? error) + { + ComputePassDescriptor passDescriptor = default; + ComputePassEncoder* passEncoder = flushContext.Api.CommandEncoderBeginComputePass(flushContext.CommandEncoder, in passDescriptor); + if (passEncoder is null) + { + error = $"Failed to begin compute pass for pipeline '{pipelineKey}'."; + return false; + } + + try + { + return this.DispatchIntoComputePass( + flushContext, + passEncoder, + pipelineKey, + shaderCode, + bindGroupLayoutFactory, + entryWriter, + dispatch, + out error); + } + finally + { + flushContext.Api.ComputePassEncoderEnd(passEncoder); + flushContext.Api.ComputePassEncoderRelease(passEncoder); + } + } + + private bool DispatchIntoComputePass( + WebGPUFlushContext flushContext, + ComputePassEncoder* passEncoder, + string pipelineKey, + ReadOnlySpan shaderCode, + WebGPUCompositeBindGroupLayoutFactory bindGroupLayoutFactory, + BindGroupEntryWriter entryWriter, + ComputePassDispatch dispatch, + out string? error) + { + if (!flushContext.DeviceState.TryGetOrCreateCompositeComputePipeline( + pipelineKey, + shaderCode, + bindGroupLayoutFactory, + out BindGroupLayout* bindGroupLayout, + out ComputePipeline* pipeline, + out error)) + { + return false; + } + + BindGroupEntry* entries = stackalloc BindGroupEntry[8]; + uint entryCount = entryWriter(new Span(entries, 8)); + + BindGroupDescriptor bindGroupDescriptor = new() + { + Layout = bindGroupLayout, + EntryCount = entryCount, + Entries = entries + }; + + BindGroup* bindGroup = flushContext.Api.DeviceCreateBindGroup(flushContext.Device, in bindGroupDescriptor); + if (bindGroup is null) + { + error = $"Failed to create bind group for pipeline '{pipelineKey}'."; + return false; + } + + flushContext.TrackBindGroup(bindGroup); + flushContext.Api.ComputePassEncoderSetPipeline(passEncoder, pipeline); + flushContext.Api.ComputePassEncoderSetBindGroup(passEncoder, 0, bindGroup, 0, null); + dispatch(passEncoder); + + error = null; + return true; + } + + /// + /// Creates the bind-group layout used by the CSR count shader. + /// + private static bool TryCreateCsrCountBindGroupLayout( + WebGPU api, + Device* device, + out BindGroupLayout* layout, + out string? error) + { + BindGroupLayoutEntry* entries = stackalloc BindGroupLayoutEntry[3]; + entries[0] = new BindGroupLayoutEntry + { + Binding = 0, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[1] = new BindGroupLayoutEntry + { + Binding = 1, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[2] = new BindGroupLayoutEntry + { + Binding = 2, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Uniform, + HasDynamicOffset = false, + MinBindingSize = sizeof(uint) + } + }; + + BindGroupLayoutDescriptor descriptor = new() + { + EntryCount = 3, + Entries = entries + }; + + layout = api.DeviceCreateBindGroupLayout(device, in descriptor); + if (layout is null) + { + error = "Failed to create CSR count bind group layout."; + return false; + } + + error = null; + return true; + } + + /// + /// Creates the bind-group layout used by the CSR scatter shader. + /// + private static bool TryCreateCsrScatterBindGroupLayout( + WebGPU api, + Device* device, + out BindGroupLayout* layout, + out string? error) + { + BindGroupLayoutEntry* entries = stackalloc BindGroupLayoutEntry[5]; + entries[0] = new BindGroupLayoutEntry + { + Binding = 0, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[1] = new BindGroupLayoutEntry + { + Binding = 1, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[2] = new BindGroupLayoutEntry + { + Binding = 2, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[3] = new BindGroupLayoutEntry + { + Binding = 3, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[4] = new BindGroupLayoutEntry + { + Binding = 4, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Uniform, + HasDynamicOffset = false, + MinBindingSize = sizeof(uint) + } + }; + + BindGroupLayoutDescriptor descriptor = new() + { + EntryCount = 5, + Entries = entries + }; + + layout = api.DeviceCreateBindGroupLayout(device, in descriptor); + if (layout is null) + { + error = "Failed to create CSR scatter bind group layout."; + return false; + } + + error = null; + return true; + } + + /// + /// CSR configuration uniform passed to count and scatter shaders. + /// + [StructLayout(LayoutKind.Sequential)] + private readonly struct CsrConfig + { + public readonly uint TotalEdgeCount; + + public CsrConfig(uint totalEdgeCount) + => this.TotalEdgeCount = totalEdgeCount; + } + + /// + /// GPU edge record matching the WGSL storage buffer layout (16 bytes, sequential). + /// Edges are pre-split at tile-row boundaries so each edge belongs to exactly one band. + /// + [StructLayout(LayoutKind.Sequential)] + private struct GpuEdge + { + public int X0; + public int Y0; + public int X1; + public int Y1; + + /// + /// Stroke edge type flags matching the WGSL shader constants. + /// + public StrokeEdgeFlags Flags; + + /// + /// Auxiliary coordinates (fixed-point). For bevel fill edges, stores the + /// join vertex V so the shader can compute the bevel triangle SDF. + /// + public int AdjX; + public int AdjY; + } + + /// + /// Transient per-definition geometry produced during edge buffer construction. + /// Edges are pre-split at tile-row boundaries and sorted by band. + /// + [StructLayout(LayoutKind.Auto)] + private struct DefinitionGeometry + { + public IMemoryOwner? EdgeOwner; + public int EdgeCount; + public int BandCount; + public IMemoryOwner? BandOffsets; + + public DefinitionGeometry( + IMemoryOwner? edgeOwner, + int edgeCount, + int bandCount, + IMemoryOwner? bandOffsets) + { + this.EdgeOwner = edgeOwner; + this.EdgeCount = edgeCount; + this.BandCount = bandCount; + this.BandOffsets = bandOffsets; + } + } + + /// + /// Describes a stroke expand command for the GPU shader. + /// Each command expands one coverage definition's centerline edges into outline edges. + /// + private record struct StrokeExpandCommand( + int DefinitionIndex, + uint InputStart, + uint InputCount, + float HalfWidth, + uint LineCap, + uint LineJoin, + float MiterLimit, + int OutlineMax, + uint OutputStart = 0, + uint OutputMax = 0, + int Band = 0); + + /// + /// GPU-side stroke expand command matching the WGSL StrokeExpandCommand struct layout. + /// + [StructLayout(LayoutKind.Sequential)] + private readonly struct GpuStrokeExpandCommand + { + public readonly uint InputStart; + public readonly uint InputCount; + public readonly uint OutputStart; + public readonly uint OutputMax; + public readonly uint HalfWidth; // f32 as bits + public readonly uint LineCap; + public readonly uint LineJoin; + public readonly uint MiterLimit; // f32 as bits + + public GpuStrokeExpandCommand(StrokeExpandCommand cmd) + { + this.InputStart = cmd.InputStart; + this.InputCount = cmd.InputCount; + this.OutputStart = cmd.OutputStart; + this.OutputMax = cmd.OutputMax; + this.HalfWidth = FloatToUInt32Bits(cmd.HalfWidth); + this.LineCap = cmd.LineCap; + this.LineJoin = cmd.LineJoin; + this.MiterLimit = FloatToUInt32Bits(cmd.MiterLimit); + } + } + + /// + /// GPU-side stroke expand config matching the WGSL StrokeExpandConfig struct layout. + /// + [StructLayout(LayoutKind.Sequential)] + private readonly struct GpuStrokeExpandConfig + { + public readonly uint TotalInputEdges; + public readonly uint CommandCount; + + public GpuStrokeExpandConfig(uint totalInputEdges, uint commandCount) + { + this.TotalInputEdges = totalInputEdges; + this.CommandCount = commandCount; + } + } + + /// + /// Contains stroke expansion data needed for the GPU dispatch. + /// + private readonly struct StrokeExpandInfo + { + public readonly List? Commands; + public readonly int TotalCenterlineEdges; + + public StrokeExpandInfo(List? commands, int totalCenterlineEdges) + { + this.Commands = commands; + this.TotalCenterlineEdges = totalCenterlineEdges; + } + + public bool HasCommands => this.Commands is not null && this.Commands.Count > 0; + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.Readback.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.Readback.cs new file mode 100644 index 000000000..77d52e53c --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.Readback.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Silk.NET.WebGPU; +using Silk.NET.WebGPU.Extensions.WGPU; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using WgpuBuffer = Silk.NET.WebGPU.Buffer; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// GPU readback helpers. +/// +public sealed unsafe partial class WebGPUDrawingBackend +{ + private const int ReadbackCallbackTimeoutMilliseconds = 5000; + + /// + public bool TryReadRegion( + Configuration configuration, + ICanvasFrame target, + Rectangle sourceRectangle, + Buffer2D destination) + where TPixel : unmanaged, IPixel + { + this.ThrowIfDisposed(); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(target, nameof(target)); + Guard.NotNull(destination, nameof(destination)); + + // Readback is only available for native WebGPU targets with valid interop handles. + if (!target.TryGetNativeSurface(out NativeSurface? nativeSurface) || + !nativeSurface.TryGetCapability(out WebGPUSurfaceCapability? capability) || + capability.Device == 0 || + capability.Queue == 0 || + capability.TargetTexture == 0) + { + return false; + } + + if (!TryGetCompositeTextureFormat(out WebGPUTextureFormatId expectedFormat, out FeatureName requiredFeature) || + expectedFormat != capability.TargetFormat) + { + return false; + } + + // Convert canvas-local source coordinates to absolute native-surface coordinates. + Rectangle absoluteSource = new( + target.Bounds.X + sourceRectangle.X, + target.Bounds.Y + sourceRectangle.Y, + sourceRectangle.Width, + sourceRectangle.Height); + Rectangle surfaceBounds = new(0, 0, capability.Width, capability.Height); + Rectangle source = Rectangle.Intersect(surfaceBounds, absoluteSource); + if (source.Width <= 0 || source.Height <= 0) + { + return false; + } + + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + WebGPU api = lease.Api; + Device* device = (Device*)capability.Device; + + if (requiredFeature != FeatureName.Undefined + && !WebGPUFlushContext.GetOrCreateDeviceState(api, device).HasFeature(requiredFeature)) + { + return false; + } + + Queue* queue = (Queue*)capability.Queue; + + int pixelSizeInBytes = Unsafe.SizeOf(); + int packedRowBytes = checked(source.Width * pixelSizeInBytes); + + // WebGPU copy-to-buffer requires bytes-per-row alignment to 256 bytes. + int readbackRowBytes = Align(packedRowBytes, 256); + ulong readbackByteCount = checked((ulong)readbackRowBytes * (ulong)source.Height); + + WgpuBuffer* readbackBuffer = null; + CommandEncoder* commandEncoder = null; + CommandBuffer* commandBuffer = null; + try + { + BufferDescriptor bufferDescriptor = new() + { + Usage = BufferUsage.CopyDst | BufferUsage.MapRead, + Size = readbackByteCount, + MappedAtCreation = false + }; + + readbackBuffer = api.DeviceCreateBuffer(device, in bufferDescriptor); + if (readbackBuffer is null) + { + return false; + } + + CommandEncoderDescriptor encoderDescriptor = default; + commandEncoder = api.DeviceCreateCommandEncoder(device, in encoderDescriptor); + if (commandEncoder is null) + { + return false; + } + + // Copy only the requested source rect from the target texture into the readback buffer. + ImageCopyTexture sourceCopy = new() + { + Texture = (Texture*)capability.TargetTexture, + MipLevel = 0, + Origin = new Origin3D((uint)source.X, (uint)source.Y, 0), + Aspect = TextureAspect.All + }; + + ImageCopyBuffer destinationCopy = new() + { + Buffer = readbackBuffer, + Layout = new TextureDataLayout + { + Offset = 0, + BytesPerRow = (uint)readbackRowBytes, + RowsPerImage = (uint)source.Height + } + }; + + Extent3D copySize = new((uint)source.Width, (uint)source.Height, 1); + api.CommandEncoderCopyTextureToBuffer(commandEncoder, in sourceCopy, in destinationCopy, in copySize); + + CommandBufferDescriptor commandBufferDescriptor = default; + commandBuffer = api.CommandEncoderFinish(commandEncoder, in commandBufferDescriptor); + if (commandBuffer is null) + { + return false; + } + + api.QueueSubmit(queue, 1, ref commandBuffer); + api.CommandBufferRelease(commandBuffer); + commandBuffer = null; + api.CommandEncoderRelease(commandEncoder); + commandEncoder = null; + + // Map the GPU buffer and wait for completion before reading host-visible bytes. + BufferMapAsyncStatus mapStatus = BufferMapAsyncStatus.Unknown; + using ManualResetEventSlim mapReady = new(false); + void Callback(BufferMapAsyncStatus status, void* userData) + { + _ = userData; + mapStatus = status; + mapReady.Set(); + } + + using PfnBufferMapCallback callback = PfnBufferMapCallback.From(Callback); + api.BufferMapAsync(readbackBuffer, MapMode.Read, 0, (nuint)readbackByteCount, callback, null); + if (!WaitForMapSignal(lease.WgpuExtension, device, mapReady) || mapStatus != BufferMapAsyncStatus.Success) + { + return false; + } + + void* mapped = api.BufferGetConstMappedRange(readbackBuffer, 0, (nuint)readbackByteCount); + if (mapped is null) + { + api.BufferUnmap(readbackBuffer); + return false; + } + + try + { + ReadOnlySpan readback = new(mapped, checked((int)readbackByteCount)); + + // Copy directly from the mapped GPU buffer into the caller's buffer, + // stripping WebGPU row padding in the process. Single copy, no intermediate array. + int copyHeight = Math.Min(source.Height, destination.Height); + for (int y = 0; y < copyHeight; y++) + { + readback + .Slice(y * readbackRowBytes, packedRowBytes) + .CopyTo(MemoryMarshal.AsBytes(destination.DangerousGetRowSpan(y))); + } + + return true; + } + finally + { + api.BufferUnmap(readbackBuffer); + } + } + finally + { + if (commandBuffer is not null) + { + api.CommandBufferRelease(commandBuffer); + } + + if (commandEncoder is not null) + { + api.CommandEncoderRelease(commandEncoder); + } + + if (readbackBuffer is not null) + { + api.BufferRelease(readbackBuffer); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Align(int value, int alignment) + => ((value + alignment - 1) / alignment) * alignment; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool WaitForMapSignal(Wgpu? extension, Device* device, ManualResetEventSlim signal) + { + if (extension is null) + { + return signal.Wait(ReadbackCallbackTimeoutMilliseconds); + } + + Stopwatch stopwatch = Stopwatch.StartNew(); + while (!signal.IsSet && stopwatch.ElapsedMilliseconds < ReadbackCallbackTimeoutMilliseconds) + { + _ = extension.DevicePoll(device, true, (WrappedSubmissionIndex*)null); + } + + return signal.IsSet; + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs new file mode 100644 index 000000000..6bfbeac61 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs @@ -0,0 +1,2503 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Silk.NET.WebGPU; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using WgpuBuffer = Silk.NET.WebGPU.Buffer; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// WebGPU-backed implementation of . +/// +/// +/// +/// This backend executes scene composition on WebGPU where possible and falls back to +/// when GPU execution is unavailable for a specific command set. +/// +/// +/// High-level flush pipeline: +/// +/// +/// CompositionScene +/// -> Encoded scene stream (draw tags + draw-data stream) +/// -> Acquire flush context (native GPU surface only) +/// -> Execute one tiled scene pass (binning -> coarse -> fine) +/// -> Blit composited output back to target texture +/// -> On failure: delegate scene to DefaultDrawingBackend +/// +/// +public sealed unsafe partial class WebGPUDrawingBackend : IDrawingBackend, IDisposable +{ + private const int CompositeTileWidth = 16; + private const int CompositeTileHeight = 16; + + private const string PreparedCompositeParamsBufferKey = "prepared-composite/params"; + private const string PreparedCompositeDispatchConfigBufferKey = "prepared-composite/dispatch-config"; + private const string PreparedCompositeColorStopsBufferKey = "prepared-composite/color-stops"; + private const string StrokeExpandPipelineKey = "stroke-expand"; + private const string StrokeExpandCommandsBufferKey = "stroke-expand/commands"; + private const string StrokeExpandConfigBufferKey = "stroke-expand/config"; + private const string StrokeExpandCounterBufferKey = "stroke-expand/counter"; + private const int UniformBufferOffsetAlignment = 256; + + private readonly DefaultDrawingBackend fallbackBackend; + private static bool? isSupported; + private bool isDisposed; + + private static readonly Dictionary CompositePixelHandlers = CreateCompositePixelHandlers(); + + /// + /// Initializes a new instance of the class. + /// + public WebGPUDrawingBackend() + => this.fallbackBackend = DefaultDrawingBackend.Instance; + + /// + /// GPU brush type identifiers. Values match the WGSL brush_type field constants. + /// + private enum PreparedBrushType : uint + { + Solid = 0, + Image = 1, + LinearGradient = 2, + RadialGradient = 3, + RadialGradientTwoCircle = 4, + EllipticGradient = 5, + SweepGradient = 6, + Pattern = 7, + Recolor = 8, + } + + /// + /// Gets the testing-only diagnostic counter for total coverage preparation requests. + /// + internal int TestingPrepareCoverageCallCount { get; private set; } + + /// + /// Gets the testing-only diagnostic counter for coverage preparations executed on the GPU. + /// + internal int TestingGPUPrepareCoverageCallCount { get; private set; } + + /// + /// Gets the testing-only diagnostic counter for coverage preparations delegated to the fallback backend. + /// + internal int TestingFallbackPrepareCoverageCallCount { get; private set; } + + /// + /// Gets the testing-only diagnostic counter for total composition requests. + /// + internal int TestingCompositeCoverageCallCount { get; private set; } + + /// + /// Gets the testing-only diagnostic counter for compositions executed on the GPU. + /// + internal int TestingGPUCompositeCoverageCallCount { get; private set; } + + /// + /// Gets the testing-only diagnostic counter for compositions delegated to the fallback backend. + /// + internal int TestingFallbackCompositeCoverageCallCount { get; private set; } + + /// + /// Gets the testing-only diagnostic counter for completed prepared-coverage uses. + /// + internal int TestingReleaseCoverageCallCount { get; private set; } + + /// + /// Gets a value indicating whether the testing-only diagnostic indicates the backend completed GPU initialization. + /// + internal bool TestingIsGPUReady { get; private set; } + + /// + /// Gets a value indicating whether the testing-only diagnostic indicates GPU initialization has been attempted. + /// + internal bool TestingGPUInitializationAttempted { get; private set; } + + /// + /// Gets the testing-only diagnostic containing the last GPU initialization failure reason, if any. + /// + internal string? TestingLastGPUInitializationFailure { get; private set; } + + /// + /// Gets the testing-only diagnostic counter for live prepared coverage handles currently in use. + /// + internal int TestingLiveCoverageCount { get; private set; } + + /// + /// Gets the testing-only diagnostic counter for composition batches that used + /// the compute composition path. + /// + internal int TestingComputePathBatchCount { get; private set; } + + /// + /// Gets the cumulative number of composition commands executed on the GPU. + /// + public int DiagnosticGpuCompositeCount => this.TestingGPUCompositeCoverageCallCount; + + /// + /// Gets the cumulative number of composition commands that fell back to the CPU backend. + /// + public int DiagnosticFallbackCompositeCount => this.TestingFallbackCompositeCoverageCallCount; + + /// + /// Gets a value indicating whether WebGPU is available on the current system. + /// This probes the runtime by attempting to acquire an adapter and device. + /// The result is cached after the first probe. + /// + public bool IsSupported => isSupported ??= ProbeFullSupport(); + + /// + /// Probes whether WebGPU compute is fully supported on the current system. + /// First checks adapter/device availability in-process. If that succeeds, + /// spawns a child process via to test compute + /// pipeline creation, which can crash with an unrecoverable access violation + /// on some systems. If the remote executor is not available, falls back to + /// the device-only check. + /// + /// Returns if WebGPU compute support is available; otherwise, . + private static bool ProbeFullSupport() + { + // Step 1: Quick in-process check for adapter/device availability. + if (!ProbeSupport()) + { + return false; + } + + // Step 2: Out-of-process probe for compute pipeline support. + // DeviceCreateComputePipeline can crash with an AccessViolationException + // on some systems (e.g. Windows CI with software renderers). This native + // crash cannot be caught in managed code, so we run it in a child process. + if (!RemoteExecutor.IsSupported) + { + // If we can't spawn a child process, assume device availability is sufficient. + return true; + } + + return RemoteExecutor.Invoke(ProbeComputePipelineSupport) == 0; + } + + /// + /// Determines whether WebGPU adapter and device are available on the current system. + /// + /// This method only checks adapter and device availability. It does not attempt + /// compute pipeline creation. Use for a complete check. + /// Returns if a WebGPU device is available; otherwise, . + public static bool ProbeSupport() + { + try + { + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + return WebGPURuntime.TryGetOrCreateDevice(out _, out _, out _); + } + catch + { + return false; + } + } + + /// + /// Probes full WebGPU compute pipeline support by compiling a trivial shader and + /// creating a compute pipeline. This method may crash with an access violation on + /// systems with broken WebGPU compute support — callers should run it in a child + /// process (e.g. via RemoteExecutor) to isolate the crash. + /// + /// Exit code: 0 on success, 1 on failure. + public static int ProbeComputePipelineSupport() + { + try + { + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + if (!WebGPURuntime.TryGetOrCreateDevice(out Device* device, out _, out _)) + { + return 1; + } + + WebGPU api = lease.Api; + + ReadOnlySpan probeShader = "@compute @workgroup_size(1) fn cs_main() {}\0"u8; + fixed (byte* shaderCodePtr = probeShader) + { + ShaderModuleWGSLDescriptor wgslDescriptor = new() + { + Chain = new ChainedStruct { SType = SType.ShaderModuleWgslDescriptor }, + Code = shaderCodePtr + }; + + ShaderModuleDescriptor shaderDescriptor = new() + { + NextInChain = (ChainedStruct*)&wgslDescriptor + }; + + ShaderModule* shaderModule = api.DeviceCreateShaderModule(device, in shaderDescriptor); + if (shaderModule is null) + { + return 1; + } + + try + { + ReadOnlySpan entryPoint = "cs_main\0"u8; + fixed (byte* entryPointPtr = entryPoint) + { + ProgrammableStageDescriptor computeStage = new() + { + Module = shaderModule, + EntryPoint = entryPointPtr + }; + + PipelineLayoutDescriptor layoutDescriptor = new() + { + BindGroupLayoutCount = 0, + BindGroupLayouts = null + }; + + PipelineLayout* pipelineLayout = api.DeviceCreatePipelineLayout(device, in layoutDescriptor); + if (pipelineLayout is null) + { + return 1; + } + + try + { + ComputePipelineDescriptor pipelineDescriptor = new() + { + Layout = pipelineLayout, + Compute = computeStage + }; + + ComputePipeline* pipeline = api.DeviceCreateComputePipeline(device, in pipelineDescriptor); + if (pipeline is null) + { + return 1; + } + + api.ComputePipelineRelease(pipeline); + return 0; + } + finally + { + api.PipelineLayoutRelease(pipelineLayout); + } + } + } + finally + { + api.ShaderModuleRelease(shaderModule); + } + } + } + catch + { + return 1; + } + } + + /// + public void FlushCompositions( + Configuration configuration, + ICanvasFrame target, + CompositionScene compositionScene) + where TPixel : unmanaged, IPixel + { +#if DEBUG_TIMING + long tMethodStart = Stopwatch.GetTimestamp(); +#endif + this.ThrowIfDisposed(); + if (compositionScene.Commands.Count == 0) + { + return; + } + + // CPU-backed target — delegate directly to the CPU backend. + if (!target.TryGetNativeSurface(out _)) + { + this.fallbackBackend.FlushCompositions(configuration, target, compositionScene); + return; + } + + if (!TryGetCompositeTextureFormat(out WebGPUTextureFormatId formatId, out FeatureName requiredFeature) || + !AreAllCompositionBrushesSupported(compositionScene.Commands)) + { + int fallbackCommandCount = compositionScene.Commands.Count; + this.TestingFallbackPrepareCoverageCallCount += fallbackCommandCount; + this.TestingFallbackCompositeCoverageCallCount += fallbackCommandCount; + + this.FlushCompositionsFallback( + configuration, + target, + compositionScene, + compositionBounds: null); + + return; + } + + TextureFormat textureFormat = WebGPUTextureFormatMapper.ToSilk(formatId); + + List preparedBatches = CompositionScenePlanner.CreatePreparedBatches( + compositionScene.Commands, + target.Bounds); + if (preparedBatches.Count == 0) + { + return; + } + + Rectangle targetExtent = new(0, 0, target.Bounds.Width, target.Bounds.Height); + int commandCount = 0; + Rectangle? compositionBounds = null; + for (int batchIndex = 0; batchIndex < preparedBatches.Count; batchIndex++) + { + CompositionBatch batch = preparedBatches[batchIndex]; + List commands = batch.Commands; + for (int i = 0; i < commands.Count; i++) + { + Rectangle destination = Rectangle.Intersect(commands[i].DestinationRegion, targetExtent); + if (destination.Width <= 0 || destination.Height <= 0) + { + continue; + } + + compositionBounds = compositionBounds.HasValue + ? Rectangle.Union(compositionBounds.Value, destination) + : destination; + + commandCount++; + } + } + + if (commandCount == 0) + { + return; + } + + if (compositionBounds is null) + { + return; + } + + this.TestingCompositeCoverageCallCount += commandCount; + + compositionBounds = Rectangle.Intersect( + compositionBounds.Value, + new Rectangle(0, 0, target.Bounds.Width, target.Bounds.Height)); + if (compositionBounds.Value.Width <= 0 || compositionBounds.Value.Height <= 0) + { + return; + } + + bool gpuSuccess = false; + bool gpuReady = false; + string? failure = null; + + WebGPUFlushContext? flushContext = WebGPUFlushContext.Create( + target, + textureFormat, + requiredFeature, + configuration.MemoryAllocator); + + if (flushContext is null) + { + this.TestingFallbackPrepareCoverageCallCount += commandCount; + this.TestingFallbackCompositeCoverageCallCount += commandCount; + this.FlushCompositionsFallback( + configuration, + target, + compositionScene, + compositionBounds); + return; + } + + try + { + gpuReady = true; + this.TestingPrepareCoverageCallCount += commandCount; + this.TestingReleaseCoverageCallCount += commandCount; + + bool renderOk = this.TryRenderPreparedFlush( + flushContext, + preparedBatches, + configuration, + target.Bounds, + compositionBounds.Value, + commandCount, + out Rectangle effectiveBounds, + out failure); + + bool finalizeOk = renderOk && TryFinalizeFlush(flushContext); + + gpuSuccess = finalizeOk; + } + catch (Exception ex) + { + failure = ex.Message; + gpuSuccess = false; + } + finally + { + flushContext.Dispose(); + this.DisposeCoverageResources(); + } + + this.TestingGPUInitializationAttempted = true; + this.TestingIsGPUReady = gpuReady; + this.TestingLastGPUInitializationFailure = gpuSuccess ? null : failure; + this.TestingLiveCoverageCount = 0; + + if (gpuSuccess) + { + this.TestingGPUPrepareCoverageCallCount += commandCount; + this.TestingGPUCompositeCoverageCallCount += commandCount; + return; + } + + this.TestingFallbackPrepareCoverageCallCount += commandCount; + this.TestingFallbackCompositeCoverageCallCount += commandCount; + this.FlushCompositionsFallback( + configuration, + target, + compositionScene, + compositionBounds); + } + + /// + public ICanvasFrame CreateLayerFrame( + Configuration configuration, + ICanvasFrame parentTarget, + int width, + int height) + where TPixel : unmanaged, IPixel + { + this.ThrowIfDisposed(); + + // Try GPU texture allocation when the parent target has a native WebGPU surface. + if (TryGetCompositeTextureFormat(out WebGPUTextureFormatId formatId, out FeatureName requiredFeature) + && parentTarget.TryGetNativeSurface(out NativeSurface? parentSurface)) + { + _ = parentSurface.TryGetCapability(out WebGPUSurfaceCapability? parentCapability); + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + WebGPU api = lease.Api; + Device* device = (Device*)parentCapability!.Device; + + WebGPUFlushContext.DeviceSharedState deviceState = WebGPUFlushContext.GetOrCreateDeviceState(api, device); + if (requiredFeature == FeatureName.Undefined || deviceState.HasFeature(requiredFeature)) + { + TextureFormat textureFormat = WebGPUTextureFormatMapper.ToSilk(formatId); + TextureDescriptor textureDescriptor = new() + { + Usage = TextureUsage.TextureBinding | TextureUsage.StorageBinding | TextureUsage.CopySrc | TextureUsage.CopyDst, + Dimension = TextureDimension.Dimension2D, + Size = new Extent3D((uint)width, (uint)height, 1), + Format = textureFormat, + MipLevelCount = 1, + SampleCount = 1 + }; + + Texture* texture = api.DeviceCreateTexture(device, in textureDescriptor); + if (texture is not null) + { + TextureViewDescriptor viewDescriptor = new() + { + Format = textureFormat, + Dimension = TextureViewDimension.Dimension2D, + BaseMipLevel = 0, + MipLevelCount = 1, + BaseArrayLayer = 0, + ArrayLayerCount = 1, + Aspect = TextureAspect.All + }; + + TextureView* textureView = api.TextureCreateView(texture, in viewDescriptor); + if (textureView is not null) + { + NativeSurface surface = WebGPUNativeSurfaceFactory.Create( + parentCapability.Device, + parentCapability.Queue, + (nint)texture, + (nint)textureView, + formatId, + width, + height); + + return new NativeCanvasFrame(new Rectangle(0, 0, width, height), surface); + } + + api.TextureRelease(texture); + } + } + } + + // Fall back to CPU allocation. + return this.fallbackBackend.CreateLayerFrame(configuration, parentTarget, width, height); + } + + /// + public void ComposeLayer( + Configuration configuration, + ICanvasFrame source, + ICanvasFrame destination, + Point destinationOffset, + GraphicsOptions options) + where TPixel : unmanaged, IPixel + { + this.ThrowIfDisposed(); + + // CPU-backed destination — delegate directly. + if (!destination.TryGetNativeSurface(out _)) + { + this.fallbackBackend.ComposeLayer(configuration, source, destination, destinationOffset, options); + return; + } + + // Try the GPU compute path first. + if (this.TryComposeLayerGpu(configuration, source, destination, destinationOffset, options)) + { + return; + } + + // GPU path unavailable — stage through CPU and upload. + this.ComposeLayerFallback(configuration, source, destination, destinationOffset, options); + } + + /// + public void ReleaseFrameResources( + Configuration configuration, + ICanvasFrame target) + where TPixel : unmanaged, IPixel + { + // Release GPU texture resources for layer frames created by this backend. + if (target.TryGetNativeSurface(out NativeSurface? nativeSurface)) + { + _ = nativeSurface.TryGetCapability(out WebGPUSurfaceCapability? capability); + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + WebGPU api = lease.Api; + api.TextureViewRelease((TextureView*)capability!.TargetTextureView); + api.TextureRelease((Texture*)capability.TargetTexture); + } + else + { + // CPU-backed frame: delegate cleanup to the fallback backend. + this.fallbackBackend.ReleaseFrameResources(configuration, target); + } + } + + /// + /// Checks whether all scene commands are directly composable by WebGPU. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool AreAllCompositionBrushesSupported(IReadOnlyList commands) + where TPixel : unmanaged, IPixel + { + for (int i = 0; i < commands.Count; i++) + { + Brush brush = commands[i].Brush; + if (!IsSupportedCompositionBrush(brush)) + { + return false; + } + } + + return true; + } + + /// + /// Checks whether the brush type is supported by the WebGPU composition path. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSupportedCompositionBrush(Brush brush) + => brush is SolidBrush + or ImageBrush + or LinearGradientBrush + or RadialGradientBrush + or EllipticGradientBrush + or SweepGradientBrush + or PatternBrush + or RecolorBrush; + + /// + /// Executes the scene on the CPU fallback backend, then uploads the result + /// to the native GPU surface. + /// + private void FlushCompositionsFallback( + Configuration configuration, + ICanvasFrame target, + CompositionScene compositionScene, + Rectangle? compositionBounds) + where TPixel : unmanaged, IPixel + { + _ = target.TryGetNativeSurface(out NativeSurface? nativeSurface); + _ = nativeSurface!.TryGetCapability(out WebGPUSurfaceCapability? capability); + + Rectangle targetBounds = target.Bounds; + using Buffer2D stagingBuffer = + configuration.MemoryAllocator.Allocate2D(targetBounds.Width, targetBounds.Height, AllocationOptions.Clean); + + Buffer2DRegion stagingRegion = new(stagingBuffer); + ICanvasFrame stagingFrame = new MemoryCanvasFrame(stagingRegion); + + this.fallbackBackend.FlushCompositions(configuration, stagingFrame, compositionScene); + + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + Buffer2DRegion uploadRegion = compositionBounds is Rectangle cb && cb.Width > 0 && cb.Height > 0 + ? stagingRegion.GetSubRegion(cb) + : stagingRegion; + + uint destX = compositionBounds is Rectangle cbx ? (uint)cbx.X : 0; + uint destY = compositionBounds is Rectangle cby ? (uint)cby.Y : 0; + + WebGPUFlushContext.UploadTextureFromRegion( + lease.Api, + (Queue*)capability!.Queue, + (Texture*)capability.TargetTexture, + uploadRegion, + configuration.MemoryAllocator, + destX, + destY, + 0); + } + + /// + /// CPU fallback for layer compositing when the GPU path is unavailable but the + /// destination is a native GPU surface. + /// + private void ComposeLayerFallback( + Configuration configuration, + ICanvasFrame source, + ICanvasFrame destination, + Point destinationOffset, + GraphicsOptions options) + where TPixel : unmanaged, IPixel + { + _ = destination.TryGetNativeSurface(out NativeSurface? destSurface); + _ = destSurface!.TryGetCapability(out WebGPUSurfaceCapability? destCapability); + + MemoryAllocator allocator = configuration.MemoryAllocator; + + // Read destination and source from the GPU into CPU buffers. + using Buffer2D destBuffer = allocator.Allocate2D(destination.Bounds.Width, destination.Bounds.Height); + if (!this.TryReadRegion(configuration, destination, destination.Bounds, destBuffer)) + { + return; + } + + using Buffer2D srcBuffer = allocator.Allocate2D(source.Bounds.Width, source.Bounds.Height); + if (!this.TryReadRegion(configuration, source, source.Bounds, srcBuffer)) + { + return; + } + + { + Buffer2DRegion destRegion = new(destBuffer); + ICanvasFrame destFrame = new MemoryCanvasFrame(destRegion); + ICanvasFrame srcFrame = new MemoryCanvasFrame(new Buffer2DRegion(srcBuffer)); + + this.fallbackBackend.ComposeLayer(configuration, srcFrame, destFrame, destinationOffset, options); + + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + WebGPUFlushContext.UploadTextureFromRegion( + lease.Api, + (Queue*)destCapability!.Queue, + (Texture*)destCapability.TargetTexture, + destRegion, + configuration.MemoryAllocator); + } + } + + private bool TryRenderPreparedFlush( + WebGPUFlushContext flushContext, + List preparedBatches, + Configuration configuration, + Rectangle targetBounds, + Rectangle compositionBounds, + int commandCount, + out Rectangle effectiveCompositionBounds, + out string? error) + where TPixel : unmanaged, IPixel + { + effectiveCompositionBounds = compositionBounds; + Rectangle targetLocalBounds = Rectangle.Intersect( + new Rectangle(0, 0, flushContext.TargetBounds.Width, flushContext.TargetBounds.Height), + compositionBounds); + if (targetLocalBounds.Width <= 0 || targetLocalBounds.Height <= 0) + { + error = null; + return true; + } + + if (!flushContext.EnsureCommandEncoder()) + { + error = "Failed to create WebGPU command encoder."; + return false; + } + + // Use the target texture directly as the backdrop source. + // This avoids an extra texture allocation and target→source copy. + TextureView* backdropTextureView = flushContext.TargetView; + int sourceOriginX = targetLocalBounds.X; + int sourceOriginY = targetLocalBounds.Y; + + if (!TryCreateCompositionTexture( + flushContext, + targetLocalBounds.Width, + targetLocalBounds.Height, + out Texture* outputTexture, + out TextureView* outputTextureView, + out error)) + { + return false; + } + + int outputOriginX = 0; + int outputOriginY = 0; + + List coverageDefinitions = []; + Dictionary coverageDefinitionIndexByKey = []; + int[] batchCoverageIndices = new int[preparedBatches.Count]; + for (int i = 0; i < batchCoverageIndices.Length; i++) + { + batchCoverageIndices[i] = -1; + } + + for (int i = 0; i < preparedBatches.Count; i++) + { + CompositionBatch batch = preparedBatches[i]; + List commands = batch.Commands; + if (commands.Count == 0) + { + continue; + } + + CoverageDefinitionIdentity definitionIdentity = new(batch.Definition); + if (!coverageDefinitionIndexByKey.TryGetValue(definitionIdentity, out int coverageDefinitionIndex)) + { + coverageDefinitionIndex = coverageDefinitions.Count; + coverageDefinitions.Add(batch.Definition); + coverageDefinitionIndexByKey.Add(definitionIdentity, coverageDefinitionIndex); + } + + batchCoverageIndices[i] = coverageDefinitionIndex; + this.TestingComputePathBatchCount++; + } + + if (commandCount == 0) + { + error = null; + return true; + } + + // Prepare stroke definitions for GPU distance-field evaluation. + // Instead of expanding to a filled outline on the CPU, we compute + // the interest rectangle from the centerline bounds inflated by + // half the stroke width and pass the centerline edges to the GPU. + // For dashed strokes, we pre-split the path into dash segments + // on the CPU (cheap) while keeping the actual stroke coverage on GPU. + for (int i = 0; i < coverageDefinitions.Count; i++) + { + CompositionCoverageDefinition definition = coverageDefinitions[i]; + if (!definition.IsStroke) + { + continue; + } + + IPath strokePath = definition.Path; + + // For dashed strokes, split the path into dash segments. + // This reuses the outline generation with a minimal width to + // produce the dash-split centerline path, but instead we use + // the dedicated dash splitting API. + if (definition.StrokePattern.Length > 0) + { + // For dashed strokes, split the path into dash segments on the CPU + // so the GPU evaluates solid strokes on each dash segment. + strokePath = strokePath.GenerateDashes(definition.StrokeWidth, definition.StrokePattern.Span); + } + + float halfWidth = definition.StrokeWidth * 0.5f; + float maxExtent = halfWidth * Math.Max((float)(definition.StrokeOptions?.MiterLimit ?? 4.0), 1.0f); + + RectangleF pathBounds = strokePath.Bounds; + pathBounds = new RectangleF( + pathBounds.X + 0.5F - maxExtent, + pathBounds.Y + 0.5F - maxExtent, + pathBounds.Width + (maxExtent * 2), + pathBounds.Height + (maxExtent * 2)); + + Rectangle interest = Rectangle.FromLTRB( + (int)MathF.Floor(pathBounds.Left), + (int)MathF.Floor(pathBounds.Top), + (int)MathF.Ceiling(pathBounds.Right), + (int)MathF.Ceiling(pathBounds.Bottom)); + + RasterizerOptions opts = definition.RasterizerOptions; + coverageDefinitions[i] = new CompositionCoverageDefinition( + definition.DefinitionKey, + strokePath, + new RasterizerOptions(interest, opts.IntersectionRule, opts.RasterizationMode, opts.SamplingOrigin, opts.AntialiasThreshold), + definition.DestinationOffset, + definition.StrokeOptions, + definition.StrokeWidth, + definition.StrokePattern); + + // Re-prepare all batches that reference this coverage definition. + for (int b = 0; b < preparedBatches.Count; b++) + { + if (batchCoverageIndices[b] == i) + { + CompositionScenePlanner.ReprepareBatchCommands( + preparedBatches[b].Commands, + targetBounds, + interest); + } + } + } + + // Recompute effective composition bounds from updated command destinations + // after stroke re-preparation tightened the interest rectangles. + Rectangle targetExtent = new(0, 0, flushContext.TargetBounds.Width, flushContext.TargetBounds.Height); + Rectangle? tightBounds = null; + for (int batchIndex = 0; batchIndex < preparedBatches.Count; batchIndex++) + { + List cmds = preparedBatches[batchIndex].Commands; + for (int i = 0; i < cmds.Count; i++) + { + Rectangle destination = Rectangle.Intersect(cmds[i].DestinationRegion, targetExtent); + if (destination.Width > 0 && destination.Height > 0) + { + tightBounds = tightBounds.HasValue + ? Rectangle.Union(tightBounds.Value, destination) + : destination; + } + } + } + + if (tightBounds.HasValue) + { + effectiveCompositionBounds = tightBounds.Value; + } + + if (!this.TryCreateEdgeBuffer( + flushContext, + coverageDefinitions, + configuration, + out WgpuBuffer* edgeBuffer, + out nuint edgeBufferSize, + out IMemoryOwner edgePlacements, + out _, + out _, + out WgpuBuffer* bandOffsetsBuffer, + out nuint bandOffsetsBufferSize, + out StrokeExpandInfo strokeExpandInfo, + out error)) + { + return false; + } + + // Dispatch stroke expansion before composite rasterization. + // This generates outline edges from centerline edges in a separate compute pass. + if (strokeExpandInfo.HasCommands) + { + if (!this.TryDispatchStrokeExpand( + flushContext, + edgeBuffer, + edgeBufferSize, + strokeExpandInfo, + out error)) + { + return false; + } + } + + if (!this.TryDispatchPreparedCompositeCommands( + flushContext, + backdropTextureView, + outputTextureView, + targetBounds, + targetLocalBounds, + sourceOriginX, + sourceOriginY, + outputOriginX, + outputOriginY, + preparedBatches, + batchCoverageIndices, + commandCount, + edgePlacements, + edgeBuffer, + edgeBufferSize, + bandOffsetsBuffer, + bandOffsetsBufferSize, + out error)) + { + return false; + } + + // Copy composited output back into the target texture. + CopyTextureRegion( + flushContext, + outputTexture, + 0, + 0, + flushContext.TargetTexture, + targetLocalBounds.X, + targetLocalBounds.Y, + targetLocalBounds.Width, + targetLocalBounds.Height); + + error = null; + return true; + } + + private bool TryDispatchPreparedCompositeCommands( + WebGPUFlushContext flushContext, + TextureView* backdropTextureView, + TextureView* outputTextureView, + Rectangle targetBounds, + Rectangle targetLocalBounds, + int sourceOriginX, + int sourceOriginY, + int outputOriginX, + int outputOriginY, + List preparedBatches, + int[] batchCoverageIndices, + int commandCount, + IMemoryOwner edgePlacements, + WgpuBuffer* edgeBuffer, + nuint edgeBufferSize, + WgpuBuffer* bandOffsetsBuffer, + nuint bandOffsetsBufferSize, + out string? error) + where TPixel : unmanaged, IPixel + { + error = null; + if (commandCount == 0) + { + return true; + } + + if (!CompositeComputeShader.TryGetCode(flushContext.TextureFormat, out byte[] shaderCode, out error)) + { + return false; + } + + // TryGetCode already validates format support via TryGetInputSampleType internally. + _ = CompositeComputeShader.TryGetInputSampleType(flushContext.TextureFormat, out TextureSampleType inputTextureSampleType); + + string pipelineKey = $"prepared-composite-fine/{flushContext.TextureFormat}"; + bool LayoutFactory(WebGPU api, Device* device, out BindGroupLayout* layout, out string? layoutError) + => TryCreateCompositeBindGroupLayout( + api, + device, + flushContext.TextureFormat, + inputTextureSampleType, + out layout, + out layoutError); + + if (!flushContext.DeviceState.TryGetOrCreateCompositeComputePipeline( + pipelineKey, + shaderCode, + LayoutFactory, + out BindGroupLayout* bindGroupLayout, + out ComputePipeline* pipeline, + out error)) + { + return false; + } + + int tileCountX = checked((int)DivideRoundUp(targetLocalBounds.Width, CompositeTileWidth)); + int tileCountY = checked((int)DivideRoundUp(targetLocalBounds.Height, CompositeTileHeight)); + int tileCount = checked(tileCountX * tileCountY); + if (tileCount == 0) + { + return true; + } + + uint parameterSize = (uint)Unsafe.SizeOf(); + IMemoryOwner parametersOwner = + flushContext.MemoryAllocator.Allocate(commandCount); + List colorStopsList = []; + try + { + int flushCommandCount = commandCount; + Span parameters = parametersOwner.Memory.Span; + TextureView* brushTextureView = backdropTextureView; + nint brushTextureViewHandle = (nint)backdropTextureView; + bool hasImageTexture = false; + + ReadOnlySpan edgePlacementsSpan = edgePlacements.Memory.Span; + int commandIndex = 0; + for (int batchIndex = 0; batchIndex < preparedBatches.Count; batchIndex++) + { + int coverageDefinitionIndex = batchCoverageIndices[batchIndex]; + if (coverageDefinitionIndex < 0) + { + continue; + } + + List commands = preparedBatches[batchIndex].Commands; + for (int i = 0; i < commands.Count; i++) + { + PreparedCompositionCommand command = commands[i]; + + PreparedBrushType brushType; + int brushOriginX = 0; + int brushOriginY = 0; + int brushRegionX = 0; + int brushRegionY = 0; + int brushRegionWidth = 1; + int brushRegionHeight = 1; + Vector4 solidColor = default; + uint gp4 = 0, gp5 = 0, gp6 = 0, gp7 = 0; + uint stopsOffset = 0, stopCount = 0; + + if (command.Brush is SolidBrush solidBrush) + { + brushType = PreparedBrushType.Solid; + solidColor = solidBrush.Color.ToScaledVector4(); + } + else if (command.Brush is ImageBrush imageBrush) + { + brushType = PreparedBrushType.Image; + Image image = (Image)imageBrush.SourceImage; + + if (!TryGetOrCreateImageTextureView( + flushContext, + image, + flushContext.TextureFormat, + out TextureView* resolvedBrushTextureView, + out error)) + { + return false; + } + + if (!hasImageTexture) + { + brushTextureView = resolvedBrushTextureView; + brushTextureViewHandle = (nint)resolvedBrushTextureView; + hasImageTexture = true; + } + else if (brushTextureViewHandle != (nint)resolvedBrushTextureView) + { + error = "Prepared composite flush currently supports one image brush texture per dispatch."; + return false; + } + + Rectangle sourceRegion = Rectangle.Intersect(image.Bounds, (Rectangle)imageBrush.SourceRegion); + brushRegionX = sourceRegion.X; + brushRegionY = sourceRegion.Y; + brushRegionWidth = sourceRegion.Width; + brushRegionHeight = sourceRegion.Height; + brushOriginX = command.BrushBounds.X + imageBrush.Offset.X - targetBounds.X - targetLocalBounds.X; + brushOriginY = command.BrushBounds.Y + imageBrush.Offset.Y - targetBounds.Y - targetLocalBounds.Y; + } + else if (command.Brush is LinearGradientBrush linearBrush) + { + brushType = PreparedBrushType.LinearGradient; + PointF start = linearBrush.StartPoint; + PointF end = linearBrush.EndPoint; + + solidColor = new Vector4(start.X, start.Y, end.X, end.Y); + gp4 = (uint)linearBrush.RepetitionMode; + PackColorStops(linearBrush.ColorStops, colorStopsList, out stopsOffset, out stopCount); + } + else if (command.Brush is RadialGradientBrush radialBrush) + { + if (radialBrush.IsTwoCircle) + { + brushType = PreparedBrushType.RadialGradientTwoCircle; + + // Pass raw brush properties; shader computes derived values. + solidColor = new Vector4(radialBrush.Center0.X, radialBrush.Center0.Y, radialBrush.Center1!.Value.X, radialBrush.Center1.Value.Y); + gp4 = FloatToUInt32Bits(radialBrush.Radius0); + gp5 = FloatToUInt32Bits(radialBrush.Radius1!.Value); + gp6 = (uint)radialBrush.RepetitionMode; + } + else + { + brushType = PreparedBrushType.RadialGradient; + + // Pass raw brush properties; shader computes derived values. + solidColor = new Vector4(radialBrush.Center0.X, radialBrush.Center0.Y, radialBrush.Radius0, 0f); + gp4 = (uint)radialBrush.RepetitionMode; + } + + PackColorStops(radialBrush.ColorStops, colorStopsList, out stopsOffset, out stopCount); + } + else if (command.Brush is EllipticGradientBrush ellipticBrush) + { + brushType = PreparedBrushType.EllipticGradient; + + // Pass raw brush properties; shader computes rotation and radii. + solidColor = new Vector4(ellipticBrush.Center.X, ellipticBrush.Center.Y, ellipticBrush.ReferenceAxisEnd.X, ellipticBrush.ReferenceAxisEnd.Y); + gp4 = FloatToUInt32Bits(ellipticBrush.AxisRatio); + gp5 = (uint)ellipticBrush.RepetitionMode; + PackColorStops(ellipticBrush.ColorStops, colorStopsList, out stopsOffset, out stopCount); + } + else if (command.Brush is SweepGradientBrush sweepBrush) + { + brushType = PreparedBrushType.SweepGradient; + + // Pass raw brush properties; shader computes radians and sweep. + solidColor = new Vector4(sweepBrush.Center.X, sweepBrush.Center.Y, sweepBrush.StartAngleDegrees, sweepBrush.EndAngleDegrees); + gp4 = (uint)sweepBrush.RepetitionMode; + PackColorStops(sweepBrush.ColorStops, colorStopsList, out stopsOffset, out stopCount); + } + else if (command.Brush is PatternBrush patternBrush) + { + brushType = PreparedBrushType.Pattern; + DenseMatrix pattern = patternBrush.Pattern; + solidColor = new Vector4(pattern.Columns, pattern.Rows, 0f, 0f); + PackPatternColors(pattern, colorStopsList, out stopsOffset); + } + else if (command.Brush is RecolorBrush recolorBrush) + { + brushType = PreparedBrushType.Recolor; + Vector4 src = recolorBrush.SourceColor.ToScaledVector4(); + Vector4 tgt = recolorBrush.TargetColor.ToScaledVector4(); + solidColor = src; + gp4 = FloatToUInt32Bits(tgt.X); + gp5 = FloatToUInt32Bits(tgt.Y); + gp6 = FloatToUInt32Bits(tgt.Z); + gp7 = FloatToUInt32Bits(tgt.W); + stopsOffset = FloatToUInt32Bits(recolorBrush.Threshold); + } + else + { + error = "Unsupported brush type."; + return false; + } + + EdgePlacement edgePlacement = edgePlacementsSpan[coverageDefinitionIndex]; + Rectangle destinationRegion = command.DestinationRegion; + Point sourceOffset = command.SourceOffset; + + int destinationX = destinationRegion.X - targetLocalBounds.X; + int destinationY = destinationRegion.Y - targetLocalBounds.Y; + + // Edge origin: transforms target-local pixel to edge-local space. + // edge_local = pixel - edge_origin, where edge_origin = destination - sourceOffset. + int edgeOriginX = destinationX - sourceOffset.X; + int edgeOriginY = destinationY - sourceOffset.Y; + + PreparedCompositeParameters commandParameters = new( + destinationX, + destinationY, + destinationRegion.Width, + destinationRegion.Height, + edgePlacement.EdgeStart, + edgePlacement.FillRule, + edgeOriginX, + edgeOriginY, + edgePlacement.CsrOffsetsStart, + edgePlacement.CsrBandCount, + brushType, + brushOriginX, + brushOriginY, + brushRegionX, + brushRegionY, + brushRegionWidth, + brushRegionHeight, + (uint)command.GraphicsOptions.ColorBlendingMode, + (uint)command.GraphicsOptions.AlphaCompositionMode, + command.GraphicsOptions.BlendPercentage, + solidColor, + command.GraphicsOptions.Antialias ? 0u : 1u, + command.GraphicsOptions.AntialiasThreshold, + gp4, + gp5, + gp6, + gp7, + stopsOffset, + stopCount); + + parameters[commandIndex] = commandParameters; + commandIndex++; + } + } + + int usedParameterByteCount = checked(flushCommandCount * (int)parameterSize); + if (!flushContext.DeviceState.TryGetOrCreateSharedBuffer( + PreparedCompositeParamsBufferKey, + BufferUsage.Storage | BufferUsage.CopyDst, + (nuint)usedParameterByteCount, + out WgpuBuffer* paramsBuffer, + out _, + out error)) + { + return false; + } + + fixed (PreparedCompositeParameters* usedParametersPtr = parameters) + { + flushContext.Api.QueueWriteBuffer( + flushContext.Queue, + paramsBuffer, + 0, + usedParametersPtr, + (nuint)usedParameterByteCount); + } + + nuint dispatchConfigSize = (nuint)Unsafe.SizeOf(); + if (!flushContext.DeviceState.TryGetOrCreateSharedBuffer( + PreparedCompositeDispatchConfigBufferKey, + BufferUsage.Uniform | BufferUsage.CopyDst, + dispatchConfigSize, + out WgpuBuffer* dispatchConfigBuffer, + out _, + out error)) + { + return false; + } + + PreparedCompositeDispatchConfig dispatchConfig = new( + (uint)targetLocalBounds.Width, + (uint)targetLocalBounds.Height, + (uint)tileCountX, + (uint)tileCountY, + (uint)tileCount, + (uint)flushCommandCount, + (uint)sourceOriginX, + (uint)sourceOriginY, + (uint)outputOriginX, + (uint)outputOriginY, + 0, + 0, + 0, + 0, + 0, + 0); + flushContext.Api.QueueWriteBuffer( + flushContext.Queue, + dispatchConfigBuffer, + 0, + &dispatchConfig, + dispatchConfigSize); + + // Color stops / pattern buffer (binding 7). + nuint colorStopsBufferSize = colorStopsList.Count > 0 + ? (nuint)(colorStopsList.Count * sizeof(float)) + : 20; // minimum 1 ColorStop (5 × f32 = 20 bytes) + if (!flushContext.DeviceState.TryGetOrCreateSharedBuffer( + PreparedCompositeColorStopsBufferKey, + BufferUsage.Storage | BufferUsage.CopyDst, + colorStopsBufferSize, + out WgpuBuffer* colorStopsBuffer, + out _, + out error)) + { + return false; + } + + if (colorStopsList.Count > 0) + { + Span stopsSpan = CollectionsMarshal.AsSpan(colorStopsList); + fixed (float* stopsPtr = stopsSpan) + { + flushContext.Api.QueueWriteBuffer( + flushContext.Queue, + colorStopsBuffer, + 0, + stopsPtr, + (nuint)(stopsSpan.Length * sizeof(float))); + } + } + + // Band offsets are pre-computed on CPU and uploaded directly. + // Edges are pre-split at band boundaries, eliminating CSR index indirection. + BindGroupEntry* bindGroupEntries = stackalloc BindGroupEntry[8]; + bindGroupEntries[0] = new BindGroupEntry + { + Binding = 0, + Buffer = edgeBuffer, + Offset = 0, + Size = edgeBufferSize + }; + bindGroupEntries[1] = new BindGroupEntry + { + Binding = 1, + TextureView = backdropTextureView + }; + bindGroupEntries[2] = new BindGroupEntry + { + Binding = 2, + TextureView = brushTextureView + }; + bindGroupEntries[3] = new BindGroupEntry + { + Binding = 3, + TextureView = outputTextureView + }; + bindGroupEntries[4] = new BindGroupEntry + { + Binding = 4, + Buffer = paramsBuffer, + Offset = 0, + Size = (nuint)usedParameterByteCount + }; + bindGroupEntries[5] = new BindGroupEntry + { + Binding = 5, + Buffer = dispatchConfigBuffer, + Offset = 0, + Size = dispatchConfigSize + }; + bindGroupEntries[6] = new BindGroupEntry + { + Binding = 6, + Buffer = bandOffsetsBuffer, + Offset = 0, + Size = bandOffsetsBufferSize + }; + bindGroupEntries[7] = new BindGroupEntry + { + Binding = 7, + Buffer = colorStopsBuffer, + Offset = 0, + Size = colorStopsBufferSize + }; + + BindGroupDescriptor bindGroupDescriptor = new() + { + Layout = bindGroupLayout, + EntryCount = 8, + Entries = bindGroupEntries + }; + + BindGroup* bindGroup = flushContext.Api.DeviceCreateBindGroup(flushContext.Device, in bindGroupDescriptor); + if (bindGroup is null) + { + error = "Failed to create prepared composite bind group."; + return false; + } + + flushContext.TrackBindGroup(bindGroup); + ComputePassDescriptor passDescriptor = default; + ComputePassEncoder* passEncoder = flushContext.Api.CommandEncoderBeginComputePass(flushContext.CommandEncoder, in passDescriptor); + if (passEncoder is null) + { + error = "Failed to begin prepared composite compute pass."; + return false; + } + + try + { + flushContext.Api.ComputePassEncoderSetPipeline(passEncoder, pipeline); + flushContext.Api.ComputePassEncoderSetBindGroup(passEncoder, 0, bindGroup, 0, null); + flushContext.Api.ComputePassEncoderDispatchWorkgroups( + passEncoder, + (uint)tileCountX, + (uint)tileCountY, + 1); + } + finally + { + flushContext.Api.ComputePassEncoderEnd(passEncoder); + flushContext.Api.ComputePassEncoderRelease(passEncoder); + } + } + finally + { + parametersOwner.Dispose(); + } + + error = null; + return true; + } + + /// + /// Dispatches the stroke expand compute shader to generate outline edges + /// from centerline edges. Must be called before the composite dispatch + /// so the generated edges are available for the fill rasterizer. + /// + private bool TryDispatchStrokeExpand( + WebGPUFlushContext flushContext, + WgpuBuffer* edgeBuffer, + nuint edgeBufferSize, + StrokeExpandInfo expandInfo, + out string? error) + { + error = null; + if (!expandInfo.HasCommands) + { + return true; + } + + List commands = expandInfo.Commands!; + + // Create or get the pipeline. + static bool LayoutFactory(WebGPU api, Device* device, out BindGroupLayout* layout, out string? layoutError) + => TryCreateStrokeExpandBindGroupLayout(api, device, out layout, out layoutError); + + if (!flushContext.DeviceState.TryGetOrCreateCompositeComputePipeline( + StrokeExpandPipelineKey, + StrokeExpandComputeShader.Code, + LayoutFactory, + out BindGroupLayout* bindGroupLayout, + out ComputePipeline* pipeline, + out error)) + { + return false; + } + + // Build GPU command array. + int commandCount = commands.Count; + using IMemoryOwner gpuCommandsOwner = flushContext.MemoryAllocator.Allocate(commandCount); + Span gpuCommands = gpuCommandsOwner.Memory.Span; + for (int i = 0; i < commandCount; i++) + { + gpuCommands[i] = new GpuStrokeExpandCommand(commands[i]); + } + + nuint commandsSize = (nuint)(commandCount * Unsafe.SizeOf()); + if (!flushContext.DeviceState.TryGetOrCreateSharedBuffer( + StrokeExpandCommandsBufferKey, + BufferUsage.Storage | BufferUsage.CopyDst, + commandsSize, + out WgpuBuffer* commandsBuffer, + out _, + out error)) + { + return false; + } + + fixed (GpuStrokeExpandCommand* commandsPtr = &MemoryMarshal.GetReference(gpuCommands)) + { + flushContext.Api.QueueWriteBuffer( + flushContext.Queue, + commandsBuffer, + 0, + commandsPtr, + commandsSize); + } + + // Config uniform. + nuint configSize = (nuint)Unsafe.SizeOf(); + if (!flushContext.DeviceState.TryGetOrCreateSharedBuffer( + StrokeExpandConfigBufferKey, + BufferUsage.Uniform | BufferUsage.CopyDst, + configSize, + out WgpuBuffer* configBuffer, + out _, + out error)) + { + return false; + } + + GpuStrokeExpandConfig config = new( + (uint)expandInfo.TotalCenterlineEdges, + (uint)commandCount); + flushContext.Api.QueueWriteBuffer( + flushContext.Queue, + configBuffer, + 0, + &config, + configSize); + + // Atomic output counters — one u32 per command, initialized to 0. + nuint counterSize = (nuint)(expandInfo.Commands!.Count * sizeof(uint)); + if (!flushContext.DeviceState.TryGetOrCreateSharedBuffer( + StrokeExpandCounterBufferKey, + BufferUsage.Storage | BufferUsage.CopyDst, + counterSize, + out WgpuBuffer* counterBuffer, + out _, + out error)) + { + return false; + } + + // Clear the counter to 0. + flushContext.Api.CommandEncoderClearBuffer(flushContext.CommandEncoder, counterBuffer, 0, counterSize); + + // Bind group. + BindGroupEntry* bindGroupEntries = stackalloc BindGroupEntry[4]; + bindGroupEntries[0] = new BindGroupEntry + { + Binding = 0, + Buffer = edgeBuffer, + Offset = 0, + Size = edgeBufferSize + }; + bindGroupEntries[1] = new BindGroupEntry + { + Binding = 1, + Buffer = commandsBuffer, + Offset = 0, + Size = commandsSize + }; + bindGroupEntries[2] = new BindGroupEntry + { + Binding = 2, + Buffer = configBuffer, + Offset = 0, + Size = configSize + }; + bindGroupEntries[3] = new BindGroupEntry + { + Binding = 3, + Buffer = counterBuffer, + Offset = 0, + Size = counterSize + }; + + BindGroupDescriptor bindGroupDescriptor = new() + { + Layout = bindGroupLayout, + EntryCount = 4, + Entries = bindGroupEntries + }; + + BindGroup* bindGroup = flushContext.Api.DeviceCreateBindGroup(flushContext.Device, in bindGroupDescriptor); + if (bindGroup is null) + { + error = "Failed to create stroke expand bind group."; + return false; + } + + flushContext.TrackBindGroup(bindGroup); + + // Dispatch in a separate compute pass (guarantees ordering before composite pass). + ComputePassDescriptor passDescriptor = default; + ComputePassEncoder* passEncoder = flushContext.Api.CommandEncoderBeginComputePass( + flushContext.CommandEncoder, in passDescriptor); + if (passEncoder is null) + { + error = "Failed to begin stroke expand compute pass."; + return false; + } + + try + { + uint workgroupCount = DivideRoundUp(expandInfo.TotalCenterlineEdges, 256); + flushContext.Api.ComputePassEncoderSetPipeline(passEncoder, pipeline); + flushContext.Api.ComputePassEncoderSetBindGroup(passEncoder, 0, bindGroup, 0, null); + flushContext.Api.ComputePassEncoderDispatchWorkgroups(passEncoder, workgroupCount, 1, 1); + } + finally + { + flushContext.Api.ComputePassEncoderEnd(passEncoder); + flushContext.Api.ComputePassEncoderRelease(passEncoder); + } + + return true; + } + + private static bool TryCreateStrokeExpandBindGroupLayout( + WebGPU api, + Device* device, + out BindGroupLayout* layout, + out string? error) + { + layout = null; + BindGroupLayoutEntry* entries = stackalloc BindGroupLayoutEntry[4]; + entries[0] = new BindGroupLayoutEntry + { + Binding = 0, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[1] = new BindGroupLayoutEntry + { + Binding = 1, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[2] = new BindGroupLayoutEntry + { + Binding = 2, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Uniform, + HasDynamicOffset = false, + MinBindingSize = (nuint)Unsafe.SizeOf() + } + }; + entries[3] = new BindGroupLayoutEntry + { + Binding = 3, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = sizeof(uint) + } + }; + + BindGroupLayoutDescriptor descriptor = new() + { + EntryCount = 4, + Entries = entries + }; + + layout = api.DeviceCreateBindGroupLayout(device, in descriptor); + if (layout is null) + { + error = "Failed to create stroke expand bind group layout."; + return false; + } + + error = null; + return true; + } + + private static bool TryGetOrCreateImageTextureView( + WebGPUFlushContext flushContext, + Image image, + TextureFormat textureFormat, + out TextureView* textureView, + out string? error) + where TPixel : unmanaged, IPixel + { + if (flushContext.TryGetCachedSourceTextureView(image, out textureView)) + { + error = null; + return true; + } + + TextureDescriptor descriptor = new() + { + Usage = TextureUsage.TextureBinding | TextureUsage.CopyDst, + Dimension = TextureDimension.Dimension2D, + Size = new Extent3D((uint)image.Width, (uint)image.Height, 1), + Format = textureFormat, + MipLevelCount = 1, + SampleCount = 1 + }; + + Texture* texture = flushContext.Api.DeviceCreateTexture(flushContext.Device, in descriptor); + if (texture is null) + { + textureView = null; + error = "Failed to create image texture."; + return false; + } + + TextureViewDescriptor viewDescriptor = new() + { + Format = descriptor.Format, + Dimension = TextureViewDimension.Dimension2D, + BaseMipLevel = 0, + MipLevelCount = 1, + BaseArrayLayer = 0, + ArrayLayerCount = 1, + Aspect = TextureAspect.All + }; + + textureView = flushContext.Api.TextureCreateView(texture, in viewDescriptor); + if (textureView is null) + { + flushContext.Api.TextureRelease(texture); + error = "Failed to create image texture view."; + return false; + } + + flushContext.TrackTexture(texture); + flushContext.TrackTextureView(textureView); + flushContext.CacheSourceTextureView(image, textureView); + + Buffer2DRegion region = new(image.Frames.RootFrame.PixelBuffer, image.Bounds); + WebGPUFlushContext.UploadTextureFromRegion( + flushContext.Api, + flushContext.Queue, + texture, + region, + flushContext.MemoryAllocator); + + error = null; + return true; + } + + /// + /// Creates the bind-group layout used by prepared composite compute shader. + /// + private static bool TryCreateCompositeBindGroupLayout( + WebGPU api, + Device* device, + TextureFormat outputTextureFormat, + TextureSampleType inputTextureSampleType, + out BindGroupLayout* layout, + out string? error) + { + BindGroupLayoutEntry* entries = stackalloc BindGroupLayoutEntry[8]; + entries[0] = new BindGroupLayoutEntry + { + Binding = 0, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[1] = new BindGroupLayoutEntry + { + Binding = 1, + Visibility = ShaderStage.Compute, + Texture = new TextureBindingLayout + { + SampleType = inputTextureSampleType, + ViewDimension = TextureViewDimension.Dimension2D, + Multisampled = false + } + }; + entries[2] = new BindGroupLayoutEntry + { + Binding = 2, + Visibility = ShaderStage.Compute, + Texture = new TextureBindingLayout + { + SampleType = inputTextureSampleType, + ViewDimension = TextureViewDimension.Dimension2D, + Multisampled = false + } + }; + entries[3] = new BindGroupLayoutEntry + { + Binding = 3, + Visibility = ShaderStage.Compute, + StorageTexture = new StorageTextureBindingLayout + { + Access = StorageTextureAccess.WriteOnly, + Format = outputTextureFormat, + ViewDimension = TextureViewDimension.Dimension2D + } + }; + entries[4] = new BindGroupLayoutEntry + { + Binding = 4, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[5] = new BindGroupLayoutEntry + { + Binding = 5, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Uniform, + HasDynamicOffset = false, + MinBindingSize = (nuint)Unsafe.SizeOf() + } + }; + entries[6] = new BindGroupLayoutEntry + { + Binding = 6, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[7] = new BindGroupLayoutEntry + { + Binding = 7, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + + BindGroupLayoutDescriptor descriptor = new() + { + EntryCount = 8, + Entries = entries + }; + + layout = api.DeviceCreateBindGroupLayout(device, in descriptor); + if (layout is null) + { + error = "Failed to create prepared composite fine bind group layout."; + return false; + } + + error = null; + return true; + } + + private static bool TryCreateCsrPrefixLocalBindGroupLayout( + WebGPU api, + Device* device, + out BindGroupLayout* layout, + out string? error) + { + BindGroupLayoutEntry* entries = stackalloc BindGroupLayoutEntry[4]; + entries[0] = new BindGroupLayoutEntry + { + Binding = 0, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[1] = new BindGroupLayoutEntry + { + Binding = 1, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[2] = new BindGroupLayoutEntry + { + Binding = 2, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[3] = new BindGroupLayoutEntry + { + Binding = 3, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Uniform, + HasDynamicOffset = false, + MinBindingSize = (nuint)Unsafe.SizeOf() + } + }; + + BindGroupLayoutDescriptor descriptor = new() + { + EntryCount = 4, + Entries = entries + }; + + layout = api.DeviceCreateBindGroupLayout(device, in descriptor); + if (layout is null) + { + error = "Failed to create prepared composite tile-prefix-local bind group layout."; + return false; + } + + error = null; + return true; + } + + private static bool TryCreateCsrPrefixBlockScanBindGroupLayout( + WebGPU api, + Device* device, + out BindGroupLayout* layout, + out string? error) + { + BindGroupLayoutEntry* entries = stackalloc BindGroupLayoutEntry[2]; + entries[0] = new BindGroupLayoutEntry + { + Binding = 0, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[1] = new BindGroupLayoutEntry + { + Binding = 1, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Uniform, + HasDynamicOffset = false, + MinBindingSize = sizeof(uint) + } + }; + + BindGroupLayoutDescriptor descriptor = new() + { + EntryCount = 2, + Entries = entries + }; + + layout = api.DeviceCreateBindGroupLayout(device, in descriptor); + if (layout is null) + { + error = "Failed to create prepared composite tile-prefix-block-scan bind group layout."; + return false; + } + + error = null; + return true; + } + + private static bool TryCreateCsrPrefixPropagateBindGroupLayout( + WebGPU api, + Device* device, + out BindGroupLayout* layout, + out string? error) + { + BindGroupLayoutEntry* entries = stackalloc BindGroupLayoutEntry[3]; + entries[0] = new BindGroupLayoutEntry + { + Binding = 0, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.ReadOnlyStorage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[1] = new BindGroupLayoutEntry + { + Binding = 1, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Storage, + HasDynamicOffset = false, + MinBindingSize = 0 + } + }; + entries[2] = new BindGroupLayoutEntry + { + Binding = 2, + Visibility = ShaderStage.Compute, + Buffer = new BufferBindingLayout + { + Type = BufferBindingType.Uniform, + HasDynamicOffset = false, + MinBindingSize = (nuint)Unsafe.SizeOf() + } + }; + + BindGroupLayoutDescriptor descriptor = new() + { + EntryCount = 3, + Entries = entries + }; + + layout = api.DeviceCreateBindGroupLayout(device, in descriptor); + if (layout is null) + { + error = "Failed to create prepared composite tile-prefix-propagate bind group layout."; + return false; + } + + error = null; + return true; + } + + /// + /// Creates one transient composition texture that can be rendered to, sampled from, and copied. + /// + private static bool TryCreateCompositionTexture( + WebGPUFlushContext flushContext, + int width, + int height, + out Texture* texture, + out TextureView* textureView, + out string? error) + { + textureView = null; + + TextureDescriptor textureDescriptor = new() + { + Usage = TextureUsage.TextureBinding | TextureUsage.StorageBinding | TextureUsage.CopySrc | TextureUsage.CopyDst, + Dimension = TextureDimension.Dimension2D, + Size = new Extent3D((uint)width, (uint)height, 1), + Format = flushContext.TextureFormat, + MipLevelCount = 1, + SampleCount = 1 + }; + + texture = flushContext.Api.DeviceCreateTexture(flushContext.Device, in textureDescriptor); + if (texture is null) + { + error = "Failed to create WebGPU composition texture."; + return false; + } + + TextureViewDescriptor textureViewDescriptor = new() + { + Format = flushContext.TextureFormat, + Dimension = TextureViewDimension.Dimension2D, + BaseMipLevel = 0, + MipLevelCount = 1, + BaseArrayLayer = 0, + ArrayLayerCount = 1, + Aspect = TextureAspect.All + }; + + textureView = flushContext.Api.TextureCreateView(texture, in textureViewDescriptor); + if (textureView is null) + { + flushContext.Api.TextureRelease(texture); + texture = null; + error = "Failed to create WebGPU composition texture view."; + return false; + } + + flushContext.TrackTexture(texture); + flushContext.TrackTextureView(textureView); + error = null; + return true; + } + + /// + /// Copies one texture region from source to destination texture. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyTextureRegion( + WebGPUFlushContext flushContext, + Texture* sourceTexture, + int sourceOriginX, + int sourceOriginY, + Texture* destinationTexture, + int destinationOriginX, + int destinationOriginY, + int width, + int height) + { + ImageCopyTexture source = new() + { + Texture = sourceTexture, + MipLevel = 0, + Origin = new Origin3D((uint)sourceOriginX, (uint)sourceOriginY, 0), + Aspect = TextureAspect.All + }; + + ImageCopyTexture destination = new() + { + Texture = destinationTexture, + MipLevel = 0, + Origin = new Origin3D((uint)destinationOriginX, (uint)destinationOriginY, 0), + Aspect = TextureAspect.All + }; + + Extent3D copySize = new((uint)width, (uint)height, 1); + flushContext.Api.CommandEncoderCopyTextureToTexture(flushContext.CommandEncoder, in source, in destination, in copySize); + } + + /// + /// Divides by and rounds up. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint DivideRoundUp(int value, int divisor) + => (uint)((value + divisor - 1) / divisor); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint FloatToUInt32Bits(float value) + => unchecked((uint)BitConverter.SingleToInt32Bits(value)); + + /// + /// Packs color stops into the shared float list for GPU upload. + /// Each stop is 5 floats: ratio, R, G, B, A (matching the WGSL ColorStop struct). + /// + private static void PackColorStops( + ReadOnlySpan stops, + List buffer, + out uint offset, + out uint count) + { + offset = (uint)(buffer.Count / 5); + count = (uint)stops.Length; + for (int i = 0; i < stops.Length; i++) + { + ColorStop stop = stops[i]; + Vector4 color = stop.Color.ToScaledVector4(); + buffer.Add(stop.Ratio); + buffer.Add(color.X); + buffer.Add(color.Y); + buffer.Add(color.Z); + buffer.Add(color.W); + } + } + + /// + /// Packs pattern colors into the shared float list for GPU upload. + /// Each cell is 5 floats: ratio (0), R, G, B, A (reusing the ColorStop layout). + /// + private static void PackPatternColors( + DenseMatrix pattern, + List buffer, + out uint offset) + { + offset = (uint)(buffer.Count / 5); + ReadOnlySpan data = pattern.Data; + for (int i = 0; i < data.Length; i++) + { + Vector4 color = data[i].ToScaledVector4(); + buffer.Add(0f); // ratio unused + buffer.Add(color.X); + buffer.Add(color.Y); + buffer.Add(color.Z); + buffer.Add(color.W); + } + } + + /// + /// Finalizes one flush by submitting command buffers. + /// + private static bool TryFinalizeFlush(WebGPUFlushContext flushContext) + { + flushContext.EndRenderPassIfOpen(); + return TrySubmit(flushContext); + } + + /// + /// Submits the current command encoder, if any. + /// + private static bool TrySubmit(WebGPUFlushContext flushContext) + { + CommandEncoder* commandEncoder = flushContext.CommandEncoder; + if (commandEncoder is null) + { + return true; + } + + CommandBuffer* commandBuffer = null; + try + { + CommandBufferDescriptor descriptor = default; + commandBuffer = flushContext.Api.CommandEncoderFinish(commandEncoder, in descriptor); + if (commandBuffer is null) + { + return false; + } + + flushContext.Api.QueueSubmit(flushContext.Queue, 1, ref commandBuffer); + flushContext.Api.CommandBufferRelease(commandBuffer); + commandBuffer = null; + flushContext.Api.CommandEncoderRelease(commandEncoder); + flushContext.CommandEncoder = null; + return true; + } + finally + { + if (commandBuffer is not null) + { + flushContext.Api.CommandBufferRelease(commandBuffer); + } + } + } + + /// + /// Releases all cached shared WebGPU resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.DisposeCoverageResources(); + WebGPUFlushContext.ClearDeviceStateCache(); + + this.TestingLiveCoverageCount = 0; + this.TestingIsGPUReady = false; + this.TestingGPUInitializationAttempted = false; + this.isDisposed = true; + } + + /// + /// Throws when this backend is disposed. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ThrowIfDisposed() + => ObjectDisposedException.ThrowIf(this.isDisposed, this); + + /// + /// Key that identifies a coverage definition for reuse within a flush. + /// + private readonly struct CoverageDefinitionIdentity : IEquatable + { + private readonly int definitionKey; + private readonly IPath path; + private readonly Rectangle interest; + private readonly IntersectionRule intersectionRule; + private readonly RasterizationMode rasterizationMode; + private readonly RasterizerSamplingOrigin samplingOrigin; + private readonly float antialiasThreshold; + + public CoverageDefinitionIdentity(in CompositionCoverageDefinition definition) + { + this.definitionKey = definition.DefinitionKey; + this.path = definition.Path; + this.interest = definition.RasterizerOptions.Interest; + this.intersectionRule = definition.RasterizerOptions.IntersectionRule; + this.rasterizationMode = definition.RasterizerOptions.RasterizationMode; + this.samplingOrigin = definition.RasterizerOptions.SamplingOrigin; + this.antialiasThreshold = definition.RasterizerOptions.AntialiasThreshold; + } + + /// + /// Determines whether this identity equals the provided coverage identity. + /// + /// The identity to compare. + /// when the identities describe the same coverage definition; otherwise . + public bool Equals(CoverageDefinitionIdentity other) + => this.definitionKey == other.definitionKey && + ReferenceEquals(this.path, other.path) && + this.interest.Equals(other.interest) && + this.intersectionRule == other.intersectionRule && + this.rasterizationMode == other.rasterizationMode && + this.samplingOrigin == other.samplingOrigin && + this.antialiasThreshold == other.antialiasThreshold; + + /// + public override bool Equals(object? obj) + => obj is CoverageDefinitionIdentity other && this.Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.definitionKey, + RuntimeHelpers.GetHashCode(this.path), + this.interest, + (int)this.intersectionRule, + (int)this.rasterizationMode, + (int)this.samplingOrigin, + this.antialiasThreshold); + } + + private readonly struct EdgePlacement + { + public EdgePlacement( + uint edgeStart, + uint edgeCount, + uint fillRule, + uint csrOffsetsStart, + uint csrBandCount) + { + this.EdgeStart = edgeStart; + this.EdgeCount = edgeCount; + this.FillRule = fillRule; + this.CsrOffsetsStart = csrOffsetsStart; + this.CsrBandCount = csrBandCount; + } + + public uint EdgeStart { get; } + + public uint EdgeCount { get; } + + public uint FillRule { get; } + + public uint CsrOffsetsStart { get; } + + public uint CsrBandCount { get; } + } + + /// + /// Dispatch constants shared across composite compute passes. + /// + [StructLayout(LayoutKind.Sequential)] + private readonly struct PreparedCompositeDispatchConfig + { + public readonly uint TargetWidth; + public readonly uint TargetHeight; + public readonly uint TileCountX; + public readonly uint TileCountY; + public readonly uint TileCount; + public readonly uint CommandCount; + public readonly uint SourceOriginX; + public readonly uint SourceOriginY; + public readonly uint OutputOriginX; + public readonly uint OutputOriginY; + public readonly uint WidthInBins; + public readonly uint HeightInBins; + public readonly uint BinCount; + public readonly uint PartitionCount; + public readonly uint BinningSize; + public readonly uint BinDataStart; + + public PreparedCompositeDispatchConfig( + uint targetWidth, + uint targetHeight, + uint tileCountX, + uint tileCountY, + uint tileCount, + uint commandCount, + uint sourceOriginX, + uint sourceOriginY, + uint outputOriginX, + uint outputOriginY, + uint widthInBins, + uint heightInBins, + uint binCount, + uint partitionCount, + uint binningSize, + uint binDataStart) + { + this.TargetWidth = targetWidth; + this.TargetHeight = targetHeight; + this.TileCountX = tileCountX; + this.TileCountY = tileCountY; + this.TileCount = tileCount; + this.CommandCount = commandCount; + this.SourceOriginX = sourceOriginX; + this.SourceOriginY = sourceOriginY; + this.OutputOriginX = outputOriginX; + this.OutputOriginY = outputOriginY; + this.WidthInBins = widthInBins; + this.HeightInBins = heightInBins; + this.BinCount = binCount; + this.PartitionCount = partitionCount; + this.BinningSize = binningSize; + this.BinDataStart = binDataStart; + } + } + + /// + /// Prepared composite command parameters consumed by . + /// Layout matches the WGSL Params struct exactly (32 u32 fields = 128 bytes). + /// + [StructLayout(LayoutKind.Sequential)] + private readonly struct PreparedCompositeParameters + { + public readonly uint DestinationX; + public readonly uint DestinationY; + public readonly uint DestinationWidth; + public readonly uint DestinationHeight; + public readonly uint EdgeStart; + public readonly uint FillRuleValue; + public readonly uint EdgeOriginX; + public readonly uint EdgeOriginY; + public readonly uint CsrOffsetsStart; + public readonly uint CsrBandCount; + public readonly uint BrushType; + public readonly uint BrushOriginX; + public readonly uint BrushOriginY; + public readonly uint BrushRegionX; + public readonly uint BrushRegionY; + public readonly uint BrushRegionWidth; + public readonly uint BrushRegionHeight; + public readonly uint ColorBlendMode; + public readonly uint AlphaCompositionMode; + public readonly uint BlendPercentage; + + /// General-purpose brush param 0. For solid brush: R. For gradients: see plan. + public readonly uint Gp0; + + /// General-purpose brush param 1. For solid brush: G. + public readonly uint Gp1; + + /// General-purpose brush param 2. For solid brush: B. + public readonly uint Gp2; + + /// General-purpose brush param 3. For solid brush: A. + public readonly uint Gp3; + + public readonly uint RasterizationMode; + public readonly uint AntialiasThreshold; + + /// General-purpose brush param 4. + public readonly uint Gp4; + + /// General-purpose brush param 5. + public readonly uint Gp5; + + /// General-purpose brush param 6. + public readonly uint Gp6; + + /// General-purpose brush param 7. + public readonly uint Gp7; + + /// Index into the color stop / pattern buffer. + public readonly uint StopsOffset; + + /// Number of color stops for gradient commands. + public readonly uint StopCount; + + public PreparedCompositeParameters( + int destinationX, + int destinationY, + int destinationWidth, + int destinationHeight, + uint edgeStart, + uint fillRuleValue, + int edgeOriginX, + int edgeOriginY, + uint csrOffsetsStart, + uint csrBandCount, + PreparedBrushType brushType, + int brushOriginX, + int brushOriginY, + int brushRegionX, + int brushRegionY, + int brushRegionWidth, + int brushRegionHeight, + uint colorBlendMode, + uint alphaCompositionMode, + float blendPercentage, + Vector4 solidColor, + uint rasterizationMode, + float antialiasThreshold, + uint gp4 = 0, + uint gp5 = 0, + uint gp6 = 0, + uint gp7 = 0, + uint stopsOffset = 0, + uint stopCount = 0) + { + this.DestinationX = (uint)destinationX; + this.DestinationY = (uint)destinationY; + this.DestinationWidth = (uint)destinationWidth; + this.DestinationHeight = (uint)destinationHeight; + this.EdgeStart = edgeStart; + this.FillRuleValue = fillRuleValue; + this.EdgeOriginX = unchecked((uint)edgeOriginX); + this.EdgeOriginY = unchecked((uint)edgeOriginY); + this.CsrOffsetsStart = csrOffsetsStart; + this.CsrBandCount = csrBandCount; + this.BrushType = (uint)brushType; + this.BrushOriginX = (uint)brushOriginX; + this.BrushOriginY = (uint)brushOriginY; + this.BrushRegionX = (uint)brushRegionX; + this.BrushRegionY = (uint)brushRegionY; + this.BrushRegionWidth = (uint)brushRegionWidth; + this.BrushRegionHeight = (uint)brushRegionHeight; + this.ColorBlendMode = colorBlendMode; + this.AlphaCompositionMode = alphaCompositionMode; + this.BlendPercentage = FloatToUInt32Bits(blendPercentage); + this.Gp0 = FloatToUInt32Bits(solidColor.X); + this.Gp1 = FloatToUInt32Bits(solidColor.Y); + this.Gp2 = FloatToUInt32Bits(solidColor.Z); + this.Gp3 = FloatToUInt32Bits(solidColor.W); + this.RasterizationMode = rasterizationMode; + this.AntialiasThreshold = FloatToUInt32Bits(antialiasThreshold); + this.Gp4 = gp4; + this.Gp5 = gp5; + this.Gp6 = gp6; + this.Gp7 = gp7; + this.StopsOffset = stopsOffset; + this.StopCount = stopCount; + } + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUFlushContext.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUFlushContext.cs new file mode 100644 index 000000000..c2b0433e5 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUFlushContext.cs @@ -0,0 +1,1227 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Silk.NET.WebGPU; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using WgpuBuffer = Silk.NET.WebGPU.Buffer; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Blend mode selection for render-pipeline-based composition passes. +/// +internal enum CompositePipelineBlendMode +{ + /// + /// Uses default blending behavior for the render pipeline variant. + /// + None = 0 +} + +/// +/// Per-flush WebGPU execution context created from a single frame target. +/// +internal sealed unsafe class WebGPUFlushContext : IDisposable +{ + private static readonly ConcurrentDictionary DeviceStateCache = new(); + private bool disposed; + private bool ownsTargetTexture; + private bool ownsTargetView; + private readonly List transientBindGroups = []; + private readonly List transientBuffers = []; + private readonly List transientTextureViews = []; + private readonly List transientTextures = []; + + // Flush-scoped source image cache: + // key = source Image reference, value = uploaded texture view handle. + // Handles are released when this flush context is disposed. + private readonly Dictionary cachedSourceTextureViews = new(ReferenceEqualityComparer.Instance); + + private WebGPUFlushContext( + WebGPURuntime.Lease runtimeLease, + Device* device, + Queue* queue, + in Rectangle targetBounds, + TextureFormat textureFormat, + MemoryAllocator memoryAllocator, + DeviceSharedState deviceState) + { + this.RuntimeLease = runtimeLease; + this.Api = runtimeLease.Api; + this.Device = device; + this.Queue = queue; + this.TargetBounds = targetBounds; + this.TextureFormat = textureFormat; + this.MemoryAllocator = memoryAllocator; + this.DeviceState = deviceState; + } + + /// + /// Gets the runtime lease that keeps the process-level WebGPU API alive. + /// + public WebGPURuntime.Lease RuntimeLease { get; } + + /// + /// Gets the WebGPU API facade for this flush. + /// + public WebGPU Api { get; } + + /// + /// Gets the device used to create and execute GPU resources. + /// + public Device* Device { get; } + + /// + /// Gets the queue used to submit GPU work. + /// + public Queue* Queue { get; } + + /// + /// Gets the target bounds for this flush context. + /// + public Rectangle TargetBounds { get; } + + /// + /// Gets the target texture format for this flush. + /// + public TextureFormat TextureFormat { get; } + + /// + /// Gets the allocator used for temporary CPU staging buffers in this flush context. + /// + public MemoryAllocator MemoryAllocator { get; } + + /// + /// Gets device-scoped shared caches and reusable resources. + /// + public DeviceSharedState DeviceState { get; } + + /// + /// Gets the target texture receiving render/composite output. + /// + public Texture* TargetTexture { get; private set; } + + /// + /// Gets the texture view used when binding the target texture. + /// + public TextureView* TargetView { get; private set; } + + /// + /// Gets the shared instance-data buffer used for parameter uploads. + /// + public WgpuBuffer* InstanceBuffer { get; private set; } + + /// + /// Gets the instance buffer capacity in bytes. + /// + public nuint InstanceBufferCapacity { get; private set; } + + /// + /// Gets or sets the current write offset into . + /// + public nuint InstanceBufferWriteOffset { get; internal set; } + + /// + /// Gets or sets the active command encoder. + /// + public CommandEncoder* CommandEncoder { get; set; } + + /// + /// Gets the currently open render pass encoder, if any. + /// + public RenderPassEncoder* PassEncoder { get; private set; } + + /// + /// Creates a flush context for a native WebGPU surface. + /// Returns when the frame does not expose a native surface + /// or the device lacks the required feature. + /// + /// The target frame. + /// The expected GPU texture format. + /// + /// A device feature required by the pixel type for storage binding, or + /// when no special feature is needed. + /// + /// The memory allocator for staging buffers. + /// The flush context, or when GPU execution is unavailable. + public static WebGPUFlushContext? Create( + ICanvasFrame frame, + TextureFormat expectedTextureFormat, + FeatureName requiredFeature, + MemoryAllocator memoryAllocator) + where TPixel : unmanaged, IPixel + { + WebGPUSurfaceCapability? nativeCapability = TryGetNativeSurfaceCapability(frame, expectedTextureFormat); + if (nativeCapability is null) + { + return null; + } + + WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + try + { + Device* device = (Device*)nativeCapability.Device; + Queue* queue = (Queue*)nativeCapability.Queue; + TextureFormat textureFormat = WebGPUTextureFormatMapper.ToSilk(nativeCapability.TargetFormat); + Rectangle bounds = new(0, 0, nativeCapability.Width, nativeCapability.Height); + DeviceSharedState deviceState = GetOrCreateDeviceState(lease.Api, device); + + if (requiredFeature != FeatureName.Undefined && !deviceState.HasFeature(requiredFeature)) + { + lease.Dispose(); + return null; + } + + WebGPUFlushContext context = new(lease, device, queue, in bounds, textureFormat, memoryAllocator, deviceState); + context.InitializeNativeTarget(nativeCapability); + return context; + } + catch + { + lease.Dispose(); + throw; + } + } + + /// + /// Clears all cached device-scoped shared state. + /// + public static void ClearDeviceStateCache() + { + foreach (DeviceSharedState state in DeviceStateCache.Values) + { + state.Dispose(); + } + + DeviceStateCache.Clear(); + } + + /// + /// Ensures that the instance buffer exists and can hold at least the requested number of bytes. + /// + /// The required number of bytes for the current flush. + /// The minimum allocation size to enforce when creating a new buffer. + /// if the buffer is available with sufficient capacity; otherwise . + public bool EnsureInstanceBufferCapacity(nuint requiredBytes, nuint minimumCapacityBytes) + { + if (this.InstanceBuffer is not null && this.InstanceBufferCapacity >= requiredBytes) + { + return true; + } + + if (this.InstanceBuffer is not null) + { + this.Api.BufferRelease(this.InstanceBuffer); + this.InstanceBuffer = null; + this.InstanceBufferCapacity = 0; + } + + nuint targetSize = requiredBytes > minimumCapacityBytes ? requiredBytes : minimumCapacityBytes; + BufferDescriptor descriptor = new() + { + Usage = BufferUsage.Storage | BufferUsage.CopyDst, + Size = targetSize + }; + + this.InstanceBuffer = this.Api.DeviceCreateBuffer(this.Device, in descriptor); + if (this.InstanceBuffer is null) + { + return false; + } + + this.InstanceBufferCapacity = targetSize; + return true; + } + + /// + /// Ensures that a command encoder is available for recording GPU commands. + /// + /// if an encoder is available; otherwise . + public bool EnsureCommandEncoder() + { + if (this.CommandEncoder is not null) + { + return true; + } + + CommandEncoderDescriptor descriptor = default; + this.CommandEncoder = this.Api.DeviceCreateCommandEncoder(this.Device, in descriptor); + return this.CommandEncoder is not null; + } + + /// + /// Begins a render pass that targets the specified texture view. + /// + public bool BeginRenderPass(TextureView* targetView) + => this.BeginRenderPass(targetView, loadExisting: false); + + /// + /// Begins a render pass that targets the specified texture view, optionally preserving existing contents. + /// + public bool BeginRenderPass(TextureView* targetView, bool loadExisting) + { + if (this.PassEncoder is not null) + { + return true; + } + + if (this.CommandEncoder is null || targetView is null) + { + return false; + } + + RenderPassColorAttachment colorAttachment = new() + { + View = targetView, + ResolveTarget = null, + LoadOp = loadExisting ? LoadOp.Load : LoadOp.Clear, + StoreOp = StoreOp.Store, + ClearValue = default + }; + + RenderPassDescriptor renderPassDescriptor = new() + { + ColorAttachmentCount = 1, + ColorAttachments = &colorAttachment + }; + + this.PassEncoder = this.Api.CommandEncoderBeginRenderPass(this.CommandEncoder, in renderPassDescriptor); + return this.PassEncoder is not null; + } + + /// + /// Ends and releases the current render pass if one is active. + /// + public void EndRenderPassIfOpen() + { + if (this.PassEncoder is null) + { + return; + } + + this.Api.RenderPassEncoderEnd(this.PassEncoder); + this.Api.RenderPassEncoderRelease(this.PassEncoder); + this.PassEncoder = null; + } + + /// + /// Tracks a transient bind group allocated during this flush. + /// + /// The bind group to track. + public void TrackBindGroup(BindGroup* bindGroup) + { + if (bindGroup is not null) + { + this.transientBindGroups.Add((nint)bindGroup); + } + } + + /// + /// Tracks a transient buffer allocated during this flush. + /// + public void TrackBuffer(WgpuBuffer* buffer) + { + if (buffer is not null) + { + this.transientBuffers.Add((nint)buffer); + } + } + + /// + /// Tracks a transient texture view allocated during this flush. + /// + public void TrackTextureView(TextureView* textureView) + { + if (textureView is not null) + { + this.transientTextureViews.Add((nint)textureView); + } + } + + /// + /// Tracks a transient texture allocated during this flush. + /// + public void TrackTexture(Texture* texture) + { + if (texture is not null) + { + this.transientTextures.Add((nint)texture); + } + } + + /// + /// Tries to resolve a cached source texture view for an input image. + /// + /// The source image key. + /// When this method returns , contains the cached texture view. + /// if a cached texture view exists; otherwise . + public bool TryGetCachedSourceTextureView(Image image, out TextureView* textureView) + { + if (this.cachedSourceTextureViews.TryGetValue(image, out nint handle) && handle != 0) + { + textureView = (TextureView*)handle; + return true; + } + + textureView = null; + return false; + } + + /// + /// Caches a source texture view for reuse within the flush. + /// + /// The source image key. + /// The texture view to cache. + public void CacheSourceTextureView(Image image, TextureView* textureView) + => this.cachedSourceTextureViews[image] = (nint)textureView; + + /// + /// Releases transient GPU resources owned by this flush context. + /// + public void Dispose() + { + if (this.disposed) + { + return; + } + + this.EndRenderPassIfOpen(); + + if (this.CommandEncoder is not null) + { + this.Api.CommandEncoderRelease(this.CommandEncoder); + this.CommandEncoder = null; + } + + if (this.InstanceBuffer is not null) + { + this.Api.BufferRelease(this.InstanceBuffer); + this.InstanceBuffer = null; + this.InstanceBufferCapacity = 0; + } + + this.InstanceBufferWriteOffset = 0; + + if (this.ownsTargetView && this.TargetView is not null) + { + this.Api.TextureViewRelease(this.TargetView); + } + + if (this.ownsTargetTexture && this.TargetTexture is not null) + { + this.Api.TextureRelease(this.TargetTexture); + } + + for (int i = 0; i < this.transientBindGroups.Count; i++) + { + this.Api.BindGroupRelease((BindGroup*)this.transientBindGroups[i]); + } + + for (int i = 0; i < this.transientBuffers.Count; i++) + { + this.Api.BufferRelease((WgpuBuffer*)this.transientBuffers[i]); + } + + for (int i = 0; i < this.transientTextureViews.Count; i++) + { + this.Api.TextureViewRelease((TextureView*)this.transientTextureViews[i]); + } + + for (int i = 0; i < this.transientTextures.Count; i++) + { + this.Api.TextureRelease((Texture*)this.transientTextures[i]); + } + + this.transientBindGroups.Clear(); + this.transientBuffers.Clear(); + this.transientTextureViews.Clear(); + this.transientTextures.Clear(); + + // Cache entries point to transient texture views that are released above. + this.cachedSourceTextureViews.Clear(); + + this.TargetView = null; + this.TargetTexture = null; + this.ownsTargetView = false; + this.ownsTargetTexture = false; + + this.RuntimeLease.Dispose(); + this.disposed = true; + } + + internal static DeviceSharedState GetOrCreateDeviceState(WebGPU api, Device* device) + { + nint cacheKey = (nint)device; + if (DeviceStateCache.TryGetValue(cacheKey, out DeviceSharedState? existing)) + { + return existing; + } + + DeviceSharedState created = new(api, device); + DeviceSharedState winner = DeviceStateCache.GetOrAdd(cacheKey, created); + if (!ReferenceEquals(winner, created)) + { + created.Dispose(); + } + + return winner; + } + + private void InitializeNativeTarget(WebGPUSurfaceCapability capability) + { + this.TargetTexture = (Texture*)capability.TargetTexture; + this.TargetView = (TextureView*)capability.TargetTextureView; + this.ownsTargetTexture = false; + this.ownsTargetView = false; + } + + private static WebGPUSurfaceCapability? TryGetNativeSurfaceCapability(ICanvasFrame frame, TextureFormat expectedTextureFormat) + where TPixel : unmanaged, IPixel + { + if (!frame.TryGetNativeSurface(out NativeSurface? nativeSurface) || + !nativeSurface.TryGetCapability(out WebGPUSurfaceCapability? capability)) + { + return null; + } + + if (capability.Device == 0 || + capability.Queue == 0 || + capability.TargetTextureView == 0 || + WebGPUTextureFormatMapper.ToSilk(capability.TargetFormat) != expectedTextureFormat) + { + return null; + } + + Rectangle bounds = frame.Bounds; + if (bounds.X < 0 || + bounds.Y < 0 || + bounds.Right > capability.Width || + bounds.Bottom > capability.Height) + { + return null; + } + + return capability; + } + + internal static void UploadTextureFromRegion( + WebGPU api, + Queue* queue, + Texture* destinationTexture, + Buffer2DRegion sourceRegion, + MemoryAllocator memoryAllocator) + where TPixel : unmanaged + => UploadTextureFromRegion(api, queue, destinationTexture, sourceRegion, memoryAllocator, 0, 0, 0); + + internal static void UploadTextureFromRegion( + WebGPU api, + Queue* queue, + Texture* destinationTexture, + Buffer2DRegion sourceRegion, + MemoryAllocator memoryAllocator, + uint destinationX, + uint destinationY, + uint destinationLayer) + where TPixel : unmanaged + { + int pixelSizeInBytes = Unsafe.SizeOf(); + ImageCopyTexture destination = new() + { + Texture = destinationTexture, + MipLevel = 0, + Origin = new Origin3D(destinationX, destinationY, destinationLayer), + Aspect = TextureAspect.All + }; + + Extent3D writeSize = new((uint)sourceRegion.Width, (uint)sourceRegion.Height, 1); + int rowBytes = checked(sourceRegion.Width * pixelSizeInBytes); + uint alignedRowBytes = AlignTo256((uint)rowBytes); + + if (sourceRegion.Buffer.DangerousTryGetSingleMemory(out Memory sourceMemory)) + { + int sourceStrideBytes = checked(sourceRegion.Buffer.RowStride * pixelSizeInBytes); + long directByteCount = ((long)sourceStrideBytes * (sourceRegion.Height - 1)) + rowBytes; + long packedByteCountEstimate = alignedRowBytes * sourceRegion.Height; + + // Only use the direct path when the stride satisfies WebGPU's alignment requirement. + if ((uint)sourceStrideBytes == alignedRowBytes && directByteCount <= packedByteCountEstimate * 2) + { + int startPixelIndex = checked((sourceRegion.Rectangle.Y * sourceRegion.Buffer.RowStride) + sourceRegion.Rectangle.X); + int startByteOffset = checked(startPixelIndex * pixelSizeInBytes); + int uploadByteCount = checked((int)directByteCount); + nuint uploadByteCountNuint = checked((nuint)uploadByteCount); + + TextureDataLayout layout = new() + { + Offset = 0, + BytesPerRow = (uint)sourceStrideBytes, + RowsPerImage = (uint)sourceRegion.Height + }; + + Span sourceBytes = MemoryMarshal.AsBytes(sourceMemory.Span).Slice(startByteOffset, uploadByteCount); + fixed (byte* uploadPtr = sourceBytes) + { + api.QueueWriteTexture(queue, in destination, uploadPtr, uploadByteCountNuint, in layout, in writeSize); + } + + return; + } + } + + int alignedRowBytesInt = checked((int)alignedRowBytes); + int packedByteCount = checked(alignedRowBytesInt * sourceRegion.Height); + using IMemoryOwner packedOwner = memoryAllocator.Allocate(packedByteCount, AllocationOptions.Clean); + Span packedData = packedOwner.Memory.Span; + for (int y = 0; y < sourceRegion.Height; y++) + { + ReadOnlySpan sourceRow = sourceRegion.DangerousGetRowSpan(y); + MemoryMarshal.AsBytes(sourceRow)[..rowBytes].CopyTo(packedData.Slice(y * alignedRowBytesInt, rowBytes)); + } + + TextureDataLayout packedLayout = new() + { + Offset = 0, + BytesPerRow = alignedRowBytes, + RowsPerImage = (uint)sourceRegion.Height + }; + + fixed (byte* uploadPtr = packedData) + { + api.QueueWriteTexture(queue, in destination, uploadPtr, (nuint)packedByteCount, in packedLayout, in writeSize); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint AlignTo256(uint value) => (value + 255U) & ~255U; + + /// + /// Shared device-scoped caches for pipelines, bind groups, and reusable GPU resources. + /// + internal sealed class DeviceSharedState : IDisposable + { + private readonly ConcurrentDictionary compositePipelines = new(StringComparer.Ordinal); + private readonly ConcurrentDictionary compositeComputePipelines = new(StringComparer.Ordinal); + private readonly ConcurrentDictionary sharedBuffers = new(StringComparer.Ordinal); + private readonly HashSet deviceFeatures; + private bool disposed; + + internal DeviceSharedState(WebGPU api, Device* device) + { + this.Api = api; + this.Device = device; + this.deviceFeatures = EnumerateDeviceFeatures(api, device); + } + + private static ReadOnlySpan CompositeVertexEntryPoint => "vs_main\0"u8; + + private static ReadOnlySpan CompositeFragmentEntryPoint => "fs_main\0"u8; + + private static ReadOnlySpan CompositeComputeEntryPoint => "cs_main\0"u8; + + /// + /// Gets the synchronization object used for shared state mutation. + /// + public object SyncRoot { get; } = new(); + + /// + /// Gets the WebGPU API instance used by this shared state. + /// + public WebGPU Api { get; } + + /// + /// Gets the device associated with this shared state. + /// + public Device* Device { get; } + + /// + /// Returns whether the device has the specified feature. + /// + /// The feature to check. + /// when the device has the feature; otherwise . + public bool HasFeature(FeatureName feature) + => this.deviceFeatures.Contains(feature); + + private static HashSet EnumerateDeviceFeatures(WebGPU api, Device* device) + { + if (device is null) + { + return []; + } + + int count = (int)api.DeviceEnumerateFeatures(device, (FeatureName*)null); + if (count <= 0) + { + return []; + } + + FeatureName* features = stackalloc FeatureName[count]; + api.DeviceEnumerateFeatures(device, features); + + HashSet result = new(count); + for (int i = 0; i < count; i++) + { + result.Add(features[i]); + } + + return result; + } + + /// + /// Gets or creates a graphics pipeline used for composite rendering. + /// + public bool TryGetOrCreateCompositePipeline( + string pipelineKey, + ReadOnlySpan shaderCode, + WebGPUCompositeBindGroupLayoutFactory bindGroupLayoutFactory, + TextureFormat textureFormat, + CompositePipelineBlendMode blendMode, + out BindGroupLayout* bindGroupLayout, + out RenderPipeline* pipeline, + out string? error) + { + bindGroupLayout = null; + pipeline = null; + + if (this.disposed) + { + error = "WebGPU device state is disposed."; + return false; + } + + if (string.IsNullOrWhiteSpace(pipelineKey)) + { + error = "Composite pipeline key cannot be empty."; + return false; + } + + if (shaderCode.IsEmpty) + { + error = $"Composite shader code is missing for pipeline '{pipelineKey}'."; + return false; + } + + CompositePipelineInfrastructure infrastructure = this.compositePipelines.GetOrAdd( + pipelineKey, + static _ => new CompositePipelineInfrastructure()); + + lock (infrastructure) + { + if (infrastructure.BindGroupLayout is null || + infrastructure.PipelineLayout is null || + infrastructure.ShaderModule is null) + { + if (!this.TryCreateCompositeInfrastructure( + shaderCode, + bindGroupLayoutFactory, + out BindGroupLayout* createdBindGroupLayout, + out PipelineLayout* createdPipelineLayout, + out ShaderModule* createdShaderModule, + out error)) + { + return false; + } + + infrastructure.BindGroupLayout = createdBindGroupLayout; + infrastructure.PipelineLayout = createdPipelineLayout; + infrastructure.ShaderModule = createdShaderModule; + } + + bindGroupLayout = infrastructure.BindGroupLayout; + (TextureFormat TextureFormat, CompositePipelineBlendMode BlendMode) variantKey = (textureFormat, blendMode); + if (infrastructure.Pipelines.TryGetValue(variantKey, out nint cachedPipelineHandle) && cachedPipelineHandle != 0) + { + pipeline = (RenderPipeline*)cachedPipelineHandle; + error = null; + return true; + } + + RenderPipeline* createdPipeline = this.CreateCompositePipeline( + infrastructure.PipelineLayout, + infrastructure.ShaderModule, + textureFormat, + blendMode); + if (createdPipeline is null) + { + error = $"Failed to create composite pipeline '{pipelineKey}' for format '{textureFormat}'."; + return false; + } + + infrastructure.Pipelines[variantKey] = (nint)createdPipeline; + pipeline = createdPipeline; + error = null; + return true; + } + } + + /// + /// Gets or creates a compute pipeline used for composite execution. + /// + public bool TryGetOrCreateCompositeComputePipeline( + string pipelineKey, + ReadOnlySpan shaderCode, + WebGPUCompositeBindGroupLayoutFactory bindGroupLayoutFactory, + out BindGroupLayout* bindGroupLayout, + out ComputePipeline* pipeline, + out string? error) + { + bindGroupLayout = null; + pipeline = null; + + if (this.disposed) + { + error = "WebGPU device state is disposed."; + return false; + } + + if (string.IsNullOrWhiteSpace(pipelineKey)) + { + error = "Composite compute pipeline key cannot be empty."; + return false; + } + + if (shaderCode.IsEmpty) + { + error = $"Composite compute shader code is missing for pipeline '{pipelineKey}'."; + return false; + } + + CompositeComputePipelineInfrastructure infrastructure = this.compositeComputePipelines.GetOrAdd( + pipelineKey, + static _ => new CompositeComputePipelineInfrastructure()); + + lock (infrastructure) + { + if (infrastructure.BindGroupLayout is null || + infrastructure.PipelineLayout is null || + infrastructure.ShaderModule is null) + { + if (!this.TryCreateCompositeInfrastructure( + shaderCode, + bindGroupLayoutFactory, + out BindGroupLayout* createdBindGroupLayout, + out PipelineLayout* createdPipelineLayout, + out ShaderModule* createdShaderModule, + out error)) + { + return false; + } + + infrastructure.BindGroupLayout = createdBindGroupLayout; + infrastructure.PipelineLayout = createdPipelineLayout; + infrastructure.ShaderModule = createdShaderModule; + } + + bindGroupLayout = infrastructure.BindGroupLayout; + if (infrastructure.Pipeline is not null) + { + pipeline = infrastructure.Pipeline; + error = null; + return true; + } + + ComputePipeline* createdPipeline = this.CreateCompositeComputePipeline( + infrastructure.PipelineLayout, + infrastructure.ShaderModule); + if (createdPipeline is null) + { + error = $"Failed to create composite compute pipeline '{pipelineKey}'."; + return false; + } + + infrastructure.Pipeline = createdPipeline; + pipeline = createdPipeline; + error = null; + return true; + } + } + + /// + /// Gets or creates a reusable shared buffer for device-scoped operations. + /// + public bool TryGetOrCreateSharedBuffer( + string bufferKey, + BufferUsage usage, + nuint requiredSize, + out WgpuBuffer* buffer, + out nuint capacity, + out string? error) + { + buffer = null; + capacity = 0; + + if (this.disposed) + { + error = "WebGPU device state is disposed."; + return false; + } + + if (string.IsNullOrWhiteSpace(bufferKey)) + { + error = "Shared buffer key cannot be empty."; + return false; + } + + if (requiredSize == 0) + { + error = $"Shared buffer '{bufferKey}' requires a non-zero size."; + return false; + } + + SharedBufferInfrastructure infrastructure = this.sharedBuffers.GetOrAdd( + bufferKey, + static _ => new SharedBufferInfrastructure()); + lock (infrastructure) + { + if (infrastructure.Buffer is not null && + infrastructure.Capacity >= requiredSize && + infrastructure.Usage == usage) + { + buffer = infrastructure.Buffer; + capacity = infrastructure.Capacity; + error = null; + return true; + } + + if (infrastructure.Buffer is not null) + { + this.Api.BufferRelease(infrastructure.Buffer); + infrastructure.Buffer = null; + infrastructure.Capacity = 0; + } + + BufferDescriptor descriptor = new() + { + Usage = usage, + Size = requiredSize + }; + + WgpuBuffer* createdBuffer = this.Api.DeviceCreateBuffer(this.Device, in descriptor); + if (createdBuffer is null) + { + error = $"Failed to create shared buffer '{bufferKey}'."; + return false; + } + + infrastructure.Buffer = createdBuffer; + infrastructure.Capacity = requiredSize; + infrastructure.Usage = usage; + buffer = createdBuffer; + capacity = requiredSize; + error = null; + return true; + } + } + + /// + /// Releases all cached pipelines, buffers, and CPU-target entries owned by this state. + /// + public void Dispose() + { + if (this.disposed) + { + return; + } + + foreach (CompositePipelineInfrastructure infrastructure in this.compositePipelines.Values) + { + this.ReleaseCompositeInfrastructure(infrastructure); + } + + this.compositePipelines.Clear(); + + foreach (CompositeComputePipelineInfrastructure infrastructure in this.compositeComputePipelines.Values) + { + this.ReleaseCompositeComputeInfrastructure(infrastructure); + } + + this.compositeComputePipelines.Clear(); + + foreach (SharedBufferInfrastructure infrastructure in this.sharedBuffers.Values) + { + lock (infrastructure) + { + if (infrastructure.Buffer is not null) + { + this.Api.BufferRelease(infrastructure.Buffer); + infrastructure.Buffer = null; + infrastructure.Capacity = 0; + } + } + } + + this.sharedBuffers.Clear(); + + this.disposed = true; + } + + private bool TryCreateCompositeInfrastructure( + ReadOnlySpan shaderCode, + WebGPUCompositeBindGroupLayoutFactory bindGroupLayoutFactory, + out BindGroupLayout* bindGroupLayout, + out PipelineLayout* pipelineLayout, + out ShaderModule* shaderModule, + out string? error) + { + bindGroupLayout = null; + pipelineLayout = null; + shaderModule = null; + + if (!bindGroupLayoutFactory(this.Api, this.Device, out bindGroupLayout, out error)) + { + return false; + } + + BindGroupLayout** bindGroupLayouts = stackalloc BindGroupLayout*[1]; + bindGroupLayouts[0] = bindGroupLayout; + PipelineLayoutDescriptor pipelineLayoutDescriptor = new() + { + BindGroupLayoutCount = 1, + BindGroupLayouts = bindGroupLayouts + }; + + pipelineLayout = this.Api.DeviceCreatePipelineLayout(this.Device, in pipelineLayoutDescriptor); + if (pipelineLayout is null) + { + this.Api.BindGroupLayoutRelease(bindGroupLayout); + error = "Failed to create composite pipeline layout."; + return false; + } + + shaderModule = this.CreateShaderModule(shaderCode); + + if (shaderModule is null) + { + this.Api.PipelineLayoutRelease(pipelineLayout); + this.Api.BindGroupLayoutRelease(bindGroupLayout); + error = "Failed to create composite shader module."; + return false; + } + + error = null; + return true; + } + + private RenderPipeline* CreateCompositePipeline( + PipelineLayout* pipelineLayout, + ShaderModule* shaderModule, + TextureFormat textureFormat, + CompositePipelineBlendMode blendMode) + { + ReadOnlySpan vertexEntryPoint = CompositeVertexEntryPoint; + ReadOnlySpan fragmentEntryPoint = CompositeFragmentEntryPoint; + fixed (byte* vertexEntryPointPtr = vertexEntryPoint) + { + fixed (byte* fragmentEntryPointPtr = fragmentEntryPoint) + { + return this.CreateCompositePipelineCore( + pipelineLayout, + shaderModule, + vertexEntryPointPtr, + fragmentEntryPointPtr, + textureFormat, + blendMode); + } + } + } + + private RenderPipeline* CreateCompositePipelineCore( + PipelineLayout* pipelineLayout, + ShaderModule* shaderModule, + byte* vertexEntryPointPtr, + byte* fragmentEntryPointPtr, + TextureFormat textureFormat, + CompositePipelineBlendMode blendMode) + { + _ = blendMode; + VertexState vertexState = new() + { + Module = shaderModule, + EntryPoint = vertexEntryPointPtr, + BufferCount = 0, + Buffers = null + }; + + ColorTargetState* colorTargets = stackalloc ColorTargetState[1]; + colorTargets[0] = new ColorTargetState + { + Format = textureFormat, + Blend = null, + WriteMask = ColorWriteMask.All + }; + + FragmentState fragmentState = new() + { + Module = shaderModule, + EntryPoint = fragmentEntryPointPtr, + TargetCount = 1, + Targets = colorTargets + }; + + RenderPipelineDescriptor descriptor = new() + { + Layout = pipelineLayout, + Vertex = vertexState, + Primitive = new PrimitiveState + { + Topology = PrimitiveTopology.TriangleList, + StripIndexFormat = IndexFormat.Undefined, + FrontFace = FrontFace.Ccw, + CullMode = CullMode.None + }, + DepthStencil = null, + Multisample = new MultisampleState + { + Count = 1, + Mask = uint.MaxValue, + AlphaToCoverageEnabled = false + }, + Fragment = &fragmentState + }; + + return this.Api.DeviceCreateRenderPipeline(this.Device, in descriptor); + } + + private ComputePipeline* CreateCompositeComputePipeline( + PipelineLayout* pipelineLayout, + ShaderModule* shaderModule) + { + ReadOnlySpan entryPoint = CompositeComputeEntryPoint; + fixed (byte* entryPointPtr = entryPoint) + { + ProgrammableStageDescriptor computeState = new() + { + Module = shaderModule, + EntryPoint = entryPointPtr + }; + + ComputePipelineDescriptor descriptor = new() + { + Layout = pipelineLayout, + Compute = computeState + }; + + return this.Api.DeviceCreateComputePipeline(this.Device, in descriptor); + } + } + + private ShaderModule* CreateShaderModule(ReadOnlySpan shaderCode) + { + System.Diagnostics.Debug.Assert( + !shaderCode.IsEmpty && shaderCode[^1] == 0, + "WGSL shader code must be null-terminated at the call site."); + + fixed (byte* shaderCodePtr = shaderCode) + { + ShaderModuleWGSLDescriptor wgslDescriptor = new() + { + Chain = new ChainedStruct { SType = SType.ShaderModuleWgslDescriptor }, + Code = shaderCodePtr + }; + + ShaderModuleDescriptor shaderDescriptor = new() + { + NextInChain = (ChainedStruct*)&wgslDescriptor + }; + + return this.Api.DeviceCreateShaderModule(this.Device, in shaderDescriptor); + } + } + + private void ReleaseCompositeInfrastructure(CompositePipelineInfrastructure infrastructure) + { + foreach (nint pipelineHandle in infrastructure.Pipelines.Values) + { + if (pipelineHandle != 0) + { + this.Api.RenderPipelineRelease((RenderPipeline*)pipelineHandle); + } + } + + infrastructure.Pipelines.Clear(); + + if (infrastructure.PipelineLayout is not null) + { + this.Api.PipelineLayoutRelease(infrastructure.PipelineLayout); + infrastructure.PipelineLayout = null; + } + + if (infrastructure.ShaderModule is not null) + { + this.Api.ShaderModuleRelease(infrastructure.ShaderModule); + infrastructure.ShaderModule = null; + } + + if (infrastructure.BindGroupLayout is not null) + { + this.Api.BindGroupLayoutRelease(infrastructure.BindGroupLayout); + infrastructure.BindGroupLayout = null; + } + } + + private void ReleaseCompositeComputeInfrastructure(CompositeComputePipelineInfrastructure infrastructure) + { + if (infrastructure.Pipeline is not null) + { + this.Api.ComputePipelineRelease(infrastructure.Pipeline); + infrastructure.Pipeline = null; + } + + if (infrastructure.PipelineLayout is not null) + { + this.Api.PipelineLayoutRelease(infrastructure.PipelineLayout); + infrastructure.PipelineLayout = null; + } + + if (infrastructure.ShaderModule is not null) + { + this.Api.ShaderModuleRelease(infrastructure.ShaderModule); + infrastructure.ShaderModule = null; + } + + if (infrastructure.BindGroupLayout is not null) + { + this.Api.BindGroupLayoutRelease(infrastructure.BindGroupLayout); + infrastructure.BindGroupLayout = null; + } + } + + /// + /// Shared render-pipeline infrastructure for compositing variants. + /// + private sealed class CompositePipelineInfrastructure + { + public Dictionary<(TextureFormat TextureFormat, CompositePipelineBlendMode BlendMode), nint> Pipelines { get; } = []; + + public BindGroupLayout* BindGroupLayout { get; set; } + + public PipelineLayout* PipelineLayout { get; set; } + + public ShaderModule* ShaderModule { get; set; } + } + + private sealed class CompositeComputePipelineInfrastructure + { + public BindGroupLayout* BindGroupLayout { get; set; } + + public PipelineLayout* PipelineLayout { get; set; } + + public ShaderModule* ShaderModule { get; set; } + + public ComputePipeline* Pipeline { get; set; } + } + + private sealed class SharedBufferInfrastructure + { + public WgpuBuffer* Buffer { get; set; } + + public nuint Capacity { get; set; } + + public BufferUsage Usage { get; set; } + } + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUNativeSurfaceFactory.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUNativeSurfaceFactory.cs new file mode 100644 index 000000000..014ef8f2c --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUNativeSurfaceFactory.cs @@ -0,0 +1,108 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Creates instances for externally-owned WebGPU targets. +/// +public static class WebGPUNativeSurfaceFactory +{ + /// + /// Creates a WebGPU-backed from opaque native handles. + /// + /// Canvas pixel format. + /// Opaque WGPUDevice* handle. + /// Opaque WGPUQueue* handle. + /// Opaque WGPUTexture* handle for writable uploads. + /// Opaque WGPUTextureView* handle for render target binding. + /// Texture format identifier. + /// Surface width in pixels. + /// Surface height in pixels. + /// A configured instance. + /// + /// The target texture must have been created with the TEXTURE_BINDING usage flag. + /// The backend reads the target texture for Porter-Duff backdrop sampling. + /// + public static NativeSurface Create( + nint deviceHandle, + nint queueHandle, + nint targetTextureHandle, + nint targetTextureViewHandle, + WebGPUTextureFormatId targetFormat, + int width, + int height) + where TPixel : unmanaged, IPixel + { + ValidateCommon( + deviceHandle, + queueHandle, + targetTextureViewHandle, + width, + height); + + ValidatePixelCompatibility(targetFormat); + + NativeSurface nativeSurface = new(TPixel.GetPixelTypeInfo()); + nativeSurface.SetCapability(new WebGPUSurfaceCapability( + deviceHandle, + queueHandle, + targetTextureHandle, + targetTextureViewHandle, + targetFormat, + width, + height)); + return nativeSurface; + } + + private static void ValidateCommon( + nint deviceHandle, + nint queueHandle, + nint targetTextureViewHandle, + int width, + int height) + { + if (deviceHandle == 0) + { + throw new ArgumentOutOfRangeException(nameof(deviceHandle), "Device handle must be non-zero."); + } + + if (queueHandle == 0) + { + throw new ArgumentOutOfRangeException(nameof(queueHandle), "Queue handle must be non-zero."); + } + + if (targetTextureViewHandle == 0) + { + throw new ArgumentOutOfRangeException(nameof(targetTextureViewHandle), "Texture view handle must be non-zero."); + } + + if (width <= 0) + { + throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than zero."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than zero."); + } + } + + private static void ValidatePixelCompatibility(WebGPUTextureFormatId targetFormat) + where TPixel : unmanaged, IPixel + { + if (!WebGPUDrawingBackend.TryGetCompositeTextureFormat(out WebGPUTextureFormatId expected)) + { + throw new NotSupportedException($"Pixel type '{typeof(TPixel).Name}' is not supported by the WebGPU backend."); + } + + if (expected != targetFormat) + { + throw new ArgumentException( + $"Target format '{targetFormat}' is not compatible with pixel type '{typeof(TPixel).Name}' (expected '{expected}').", + nameof(targetFormat)); + } + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.cs b/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.cs new file mode 100644 index 000000000..268f22ae9 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPURuntime.cs @@ -0,0 +1,450 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Silk.NET.WebGPU; +using Silk.NET.WebGPU.Extensions.WGPU; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Process-level WebGPU API runtime. +/// +/// +/// +/// This type owns the process-level Silk API loader, its +/// optional extension, and a lazily provisioned default +/// device/queue pair used by the GPU backend when no native surface is available. +/// +/// +/// Backends acquire access by taking a via . +/// The lease count is thread-safe and prevents accidental shutdown while active +/// backends are still running. +/// +/// +/// Runtime unload is explicit: +/// +/// +/// when there are no active leases. +/// Best-effort cleanup on process exit. +/// +/// +/// The shutdown path is resilient to duplicate native unload attempts. +/// +/// +internal static unsafe class WebGPURuntime +{ + /// + /// Synchronizes all runtime state transitions. + /// + private static readonly object Sync = new(); + + /// + /// Process-level WebGPU API loader. + /// + private static WebGPU? api; + + /// + /// Optional wgpu-native extension facade. + /// + private static Wgpu? wgpuExtension; + + /// + /// Lazily provisioned device handle for CPU-backed frames. + /// + private static nint autoDeviceHandle; + + /// + /// Lazily provisioned queue handle for CPU-backed frames. + /// + private static nint autoQueueHandle; + + /// + /// Number of currently active runtime leases. + /// + private static int leaseCount; + + /// + /// Tracks whether the process-exit hook has been installed. + /// + private static bool processExitHooked; + + /// + /// Timeout for asynchronous WebGPU callbacks. + /// + private const int CallbackTimeoutMilliseconds = 10_000; + + /// + /// Acquires a runtime lease for WebGPU access. + /// + /// A lease that must be disposed when access is no longer required. + /// Thrown when the WebGPU API cannot be initialized. + public static Lease Acquire() + { + lock (Sync) + { + if (!processExitHooked) + { + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; + processExitHooked = true; + } + + api ??= WebGPU.GetApi(); + if (api is null) + { + throw new InvalidOperationException("WebGPU.GetApi returned null."); + } + + if (wgpuExtension is null) + { + api.TryGetDeviceExtension(null, out wgpuExtension); + } + + leaseCount++; + return new Lease(api, wgpuExtension); + } + } + + /// + /// Lazily provisions and caches a default device/queue pair for CPU-backed frames. + /// Returns cached handles on subsequent calls. + /// + /// Receives the device pointer on success. + /// Receives the queue pointer on success. + /// Receives an error message on failure. + /// when handles are available; otherwise . + internal static bool TryGetOrCreateDevice(out Device* device, out Queue* queue, out string? error) + { + lock (Sync) + { + // Fast path: return cached handles. + if (autoDeviceHandle != 0 && autoQueueHandle != 0) + { + device = (Device*)autoDeviceHandle; + queue = (Queue*)autoQueueHandle; + error = null; + return true; + } + + if (api is null) + { + device = null; + queue = null; + error = "WebGPU API is not initialized. Call Acquire() first."; + return false; + } + + // Provision: instance → adapter → device → queue. + // The instance and adapter are transient; only the device and queue are cached. + Instance* instance = api.CreateInstance((InstanceDescriptor*)null); + if (instance is null) + { + device = null; + queue = null; + error = "WebGPU.CreateInstance returned null."; + return false; + } + + Adapter* adapter = null; + Device* requestedDevice = null; + Queue* requestedQueue = null; + bool initialized = false; + try + { + if (!TryRequestAdapter(api, instance, out adapter, out error)) + { + device = null; + queue = null; + return false; + } + + if (!TryRequestDevice(api, adapter, out requestedDevice, out error)) + { + device = null; + queue = null; + return false; + } + + requestedQueue = api.DeviceGetQueue(requestedDevice); + if (requestedQueue is null) + { + device = null; + queue = null; + error = "WebGPU.DeviceGetQueue returned null."; + return false; + } + + // Cache for subsequent calls. + autoDeviceHandle = (nint)requestedDevice; + autoQueueHandle = (nint)requestedQueue; + device = requestedDevice; + queue = requestedQueue; + error = null; + initialized = true; + return true; + } + finally + { + // Always release transient handles. + if (adapter is not null) + { + api.AdapterRelease(adapter); + } + + api.InstanceRelease(instance); + + // On failure, release any partially provisioned handles. + if (!initialized) + { + if (requestedQueue is not null) + { + api.QueueRelease(requestedQueue); + } + + if (requestedDevice is not null) + { + api.DeviceRelease(requestedDevice); + } + } + } + } + } + + /// + /// Releases one active runtime lease. + /// + /// + /// Lease release does not automatically unload the runtime. Unload is performed by + /// or by the process-exit handler. + /// + private static void Release() + { + lock (Sync) + { + if (leaseCount <= 0) + { + return; + } + + leaseCount--; + } + } + + /// + /// Shuts down the process-level WebGPU runtime when no leases are active. + /// + /// + /// This call is intended for coordinated application shutdown. Runtime state can be + /// reinitialized later by calling again. + /// + /// Thrown when runtime leases are still active. + public static void Shutdown() + { + lock (Sync) + { + if (leaseCount != 0) + { + throw new InvalidOperationException($"Cannot shut down WebGPU runtime while {leaseCount} lease(s) are active."); + } + + DisposeRuntimeCore(); + } + } + + /// + /// Process-exit cleanup callback. + /// + /// Event sender. + /// Event arguments. + private static void OnProcessExit(object? sender, EventArgs e) + { + _ = sender; + _ = e; + lock (Sync) + { + leaseCount = 0; + DisposeRuntimeCore(); + } + } + + /// + /// Disposes native runtime objects in a safe and idempotent way. + /// + /// + /// Duplicate-dispose exceptions are intentionally swallowed because process-exit + /// teardown may race with other shutdown paths. + /// + private static void DisposeRuntimeCore() + { + autoDeviceHandle = 0; + autoQueueHandle = 0; + + try + { + wgpuExtension?.Dispose(); + } + catch (Exception ex) when (ex is ObjectDisposedException or InvalidOperationException) + { + // Safe to ignore at process shutdown or double-dispose races. + } + finally + { + wgpuExtension = null; + } + + try + { + api?.Dispose(); + } + catch (Exception ex) when (ex is ObjectDisposedException or InvalidOperationException) + { + // Safe to ignore at process shutdown or double-dispose races. + } + finally + { + api = null; + } + } + + private static bool TryRequestAdapter(WebGPU api, Instance* instance, out Adapter* adapter, out string? error) + { + RequestAdapterStatus callbackStatus = RequestAdapterStatus.Unknown; + Adapter* callbackAdapter = null; + using ManualResetEventSlim callbackReady = new(false); + void Callback(RequestAdapterStatus status, Adapter* adapterPtr, byte* message, void* userData) + { + callbackStatus = status; + callbackAdapter = adapterPtr; + callbackReady.Set(); + } + + using PfnRequestAdapterCallback callbackPtr = PfnRequestAdapterCallback.From(Callback); + RequestAdapterOptions options = new() + { + PowerPreference = PowerPreference.HighPerformance + }; + + api.InstanceRequestAdapter(instance, in options, callbackPtr, null); + if (!callbackReady.Wait(CallbackTimeoutMilliseconds)) + { + adapter = null; + error = "Timed out while waiting for WebGPU adapter request callback."; + return false; + } + + adapter = callbackAdapter; + if (callbackStatus != RequestAdapterStatus.Success || callbackAdapter is null) + { + error = $"WebGPU adapter request failed with status '{callbackStatus}'."; + return false; + } + + error = null; + return true; + } + + private static bool TryRequestDevice(WebGPU api, Adapter* adapter, out Device* device, out string? error) + { + RequestDeviceStatus callbackStatus = RequestDeviceStatus.Unknown; + Device* callbackDevice = null; + using ManualResetEventSlim callbackReady = new(false); + void Callback(RequestDeviceStatus status, Device* devicePtr, byte* message, void* userData) + { + callbackStatus = status; + callbackDevice = devicePtr; + callbackReady.Set(); + } + + using PfnRequestDeviceCallback callbackPtr = PfnRequestDeviceCallback.From(Callback); + + // Auto-provision a device when no native surface provides one. + // Request optional storage features that are available on this adapter. + // The compute compositor needs storage binding on the transient output texture, + // and some formats (e.g. Bgra8Unorm) require explicit device features. + Span requestedFeatures = stackalloc FeatureName[1]; + int requestedCount = 0; + if (api.AdapterHasFeature(adapter, FeatureName.Bgra8UnormStorage)) + { + requestedFeatures[requestedCount++] = FeatureName.Bgra8UnormStorage; + } + + DeviceDescriptor descriptor; + if (requestedCount > 0) + { + fixed (FeatureName* featuresPtr = requestedFeatures) + { + descriptor = new DeviceDescriptor + { + RequiredFeatureCount = (uint)requestedCount, + RequiredFeatures = featuresPtr, + }; + + api.AdapterRequestDevice(adapter, in descriptor, callbackPtr, null); + } + } + else + { + descriptor = default; + api.AdapterRequestDevice(adapter, in descriptor, callbackPtr, null); + } + + if (!callbackReady.Wait(CallbackTimeoutMilliseconds)) + { + device = null; + error = "Timed out while waiting for WebGPU device request callback."; + return false; + } + + device = callbackDevice; + if (callbackStatus != RequestDeviceStatus.Success || callbackDevice is null) + { + error = $"WebGPU device request failed with status '{callbackStatus}'."; + return false; + } + + error = null; + return true; + } + + /// + /// Ref-counted access token for . + /// + /// + /// Disposing the lease decrements the runtime lease count exactly once. + /// + internal sealed class Lease : IDisposable + { + private int disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The shared WebGPU API loader. + /// The shared optional wgpu extension facade. + internal Lease(WebGPU api, Wgpu? wgpuExtension) + { + this.Api = api; + this.WgpuExtension = wgpuExtension; + } + + /// + /// Gets the shared WebGPU API loader. + /// + public WebGPU Api { get; } + + /// + /// Gets the shared optional wgpu extension facade. + /// + public Wgpu? WgpuExtension { get; } + + /// + /// Releases this lease exactly once. + /// + public void Dispose() + { + if (Interlocked.Exchange(ref this.disposed, 1) == 0) + { + Release(); + } + } + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUSurfaceCapability.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUSurfaceCapability.cs new file mode 100644 index 000000000..6a62cc51a --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUSurfaceCapability.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Native WebGPU surface capability attached to . +/// +/// +/// The handle must remain valid for the lifetime of any +/// that processes frames using this capability. +/// The backend caches per-device GPU resources (pipelines, buffers) that reference +/// the device internally. Ensure the device is not released while any backend +/// instance may still reference it. +/// +public sealed class WebGPUSurfaceCapability +{ + /// + /// Initializes a new instance of the class. + /// + /// Opaque WGPUDevice* handle. Must remain valid for the lifetime of any backend that uses this capability. + /// Opaque WGPUQueue* handle. + /// Opaque WGPUTexture* handle for the current frame when writable upload is supported. + /// Opaque WGPUTextureView* handle for the current frame. + /// Native render target texture format identifier. + /// Surface width in pixels. + /// Surface height in pixels. + public WebGPUSurfaceCapability( + nint device, + nint queue, + nint targetTexture, + nint targetTextureView, + WebGPUTextureFormatId targetFormat, + int width, + int height) + { + this.Device = device; + this.Queue = queue; + this.TargetTexture = targetTexture; + this.TargetTextureView = targetTextureView; + this.TargetFormat = targetFormat; + this.Width = width; + this.Height = height; + } + + /// + /// Gets the opaque WGPUDevice* handle. + /// + public nint Device { get; } + + /// + /// Gets the opaque WGPUQueue* handle. + /// + public nint Queue { get; } + + /// + /// Gets the opaque WGPUTexture* handle for the current frame. + /// + public nint TargetTexture { get; } + + /// + /// Gets the opaque WGPUTextureView* handle for the current frame. + /// + public nint TargetTextureView { get; } + + /// + /// Gets the native render target texture format identifier. + /// + public WebGPUTextureFormatId TargetFormat { get; } + + /// + /// Gets the surface width in pixels. + /// + public int Width { get; } + + /// + /// Gets the surface height in pixels. + /// + public int Height { get; } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUTestNativeSurfaceAllocator.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUTestNativeSurfaceAllocator.cs new file mode 100644 index 000000000..ed78b044c --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUTestNativeSurfaceAllocator.cs @@ -0,0 +1,383 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Silk.NET.WebGPU; +using Silk.NET.WebGPU.Extensions.WGPU; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Internal helper for benchmark/test-only native WebGPU target allocation. +/// +internal static unsafe class WebGPUTestNativeSurfaceAllocator +{ + private const int CallbackTimeoutMilliseconds = 5000; + + /// + /// Tries to allocate a native WebGPU texture + view pair and wrap them in a . + /// + internal static bool TryCreate( + int width, + int height, + out NativeSurface surface, + out nint textureHandle, + out nint textureViewHandle, + out string error) + where TPixel : unmanaged, IPixel + { + if (!WebGPUDrawingBackend.TryGetCompositeTextureFormat(out WebGPUTextureFormatId formatId, out FeatureName requiredFeature)) + { + surface = new NativeSurface(TPixel.GetPixelTypeInfo()); + textureHandle = 0; + textureViewHandle = 0; + error = $"Pixel type '{typeof(TPixel).Name}' is not supported by the WebGPU backend."; + return false; + } + + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + WebGPU api = lease.Api; + + if (!WebGPURuntime.TryGetOrCreateDevice(out Device* device, out Queue* queue, out string? deviceError)) + { + surface = new NativeSurface(TPixel.GetPixelTypeInfo()); + textureHandle = 0; + textureViewHandle = 0; + error = deviceError ?? "WebGPU device auto-provisioning failed."; + return false; + } + + if (requiredFeature != FeatureName.Undefined + && !WebGPUFlushContext.GetOrCreateDeviceState(api, device).HasFeature(requiredFeature)) + { + surface = new NativeSurface(TPixel.GetPixelTypeInfo()); + textureHandle = 0; + textureViewHandle = 0; + error = $"Device does not support required feature '{requiredFeature}' for pixel type '{typeof(TPixel).Name}'."; + return false; + } + + TextureFormat textureFormat = WebGPUTextureFormatMapper.ToSilk(formatId); + TextureDescriptor targetTextureDescriptor = new() + { + Usage = TextureUsage.RenderAttachment | TextureUsage.CopySrc | TextureUsage.CopyDst | TextureUsage.TextureBinding | TextureUsage.StorageBinding, + Dimension = TextureDimension.Dimension2D, + Size = new Extent3D((uint)width, (uint)height, 1), + Format = textureFormat, + MipLevelCount = 1, + SampleCount = 1 + }; + + Texture* targetTexture = api.DeviceCreateTexture(device, in targetTextureDescriptor); + if (targetTexture is null) + { + surface = new NativeSurface(TPixel.GetPixelTypeInfo()); + textureHandle = 0; + textureViewHandle = 0; + error = "WebGPU.DeviceCreateTexture returned null."; + return false; + } + + TextureViewDescriptor targetViewDescriptor = new() + { + Format = textureFormat, + Dimension = TextureViewDimension.Dimension2D, + BaseMipLevel = 0, + MipLevelCount = 1, + BaseArrayLayer = 0, + ArrayLayerCount = 1, + Aspect = TextureAspect.All + }; + + TextureView* targetView = api.TextureCreateView(targetTexture, in targetViewDescriptor); + if (targetView is null) + { + api.TextureRelease(targetTexture); + surface = new NativeSurface(TPixel.GetPixelTypeInfo()); + textureHandle = 0; + textureViewHandle = 0; + error = "WebGPU.TextureCreateView returned null."; + return false; + } + + nint deviceHandle = (nint)device; + nint queueHandle = (nint)queue; + textureHandle = (nint)targetTexture; + textureViewHandle = (nint)targetView; + surface = WebGPUNativeSurfaceFactory.Create( + deviceHandle, + queueHandle, + textureHandle, + textureViewHandle, + formatId, + width, + height); + error = string.Empty; + return true; + } + + /// + /// Tries to upload CPU pixel data to an existing native WebGPU texture handle. + /// + internal static bool TryWriteTexture( + nint textureHandle, + int width, + int height, + Image image, + out string error) + where TPixel : unmanaged, IPixel + { + if (textureHandle == 0) + { + error = "Texture handle is zero."; + return false; + } + + if (image.Width != width || image.Height != height) + { + error = "Source image dimensions must match the target texture dimensions."; + return false; + } + + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + if (!WebGPURuntime.TryGetOrCreateDevice(out _, out Queue* queue, out string? deviceError)) + { + error = deviceError ?? "WebGPU device auto-provisioning failed."; + return false; + } + + try + { + Buffer2DRegion sourceRegion = new(image.Frames.RootFrame.PixelBuffer, image.Bounds); + WebGPUFlushContext.UploadTextureFromRegion( + lease.Api, + queue, + (Texture*)textureHandle, + sourceRegion, + Configuration.Default.MemoryAllocator); + error = string.Empty; + return true; + } + catch (Exception ex) + { + error = ex.Message; + return false; + } + } + + /// + /// Tries to read pixels from a native WebGPU texture handle into an . + /// + internal static bool TryReadTexture( + nint textureHandle, + int width, + int height, + out Image? image, + out string error) + where TPixel : unmanaged, IPixel + { + image = null; + if (textureHandle == 0) + { + error = "Texture handle is zero."; + return false; + } + + if (width <= 0 || height <= 0) + { + error = "Texture dimensions must be greater than zero."; + return false; + } + + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + if (!WebGPURuntime.TryGetOrCreateDevice(out Device* device, out Queue* queue, out string? deviceError)) + { + error = deviceError ?? "WebGPU device auto-provisioning failed."; + return false; + } + + WebGPU api = lease.Api; + int pixelSizeInBytes = Unsafe.SizeOf(); + int packedRowBytes = checked(width * pixelSizeInBytes); + int readbackRowBytes = Align(packedRowBytes, 256); + int packedByteCount = checked(packedRowBytes * height); + ulong readbackByteCount = checked((ulong)readbackRowBytes * (ulong)height); + + Silk.NET.WebGPU.Buffer* readbackBuffer = null; + CommandEncoder* commandEncoder = null; + CommandBuffer* commandBuffer = null; + try + { + BufferDescriptor bufferDescriptor = new() + { + Usage = BufferUsage.CopyDst | BufferUsage.MapRead, + Size = readbackByteCount, + MappedAtCreation = false + }; + + readbackBuffer = api.DeviceCreateBuffer(device, in bufferDescriptor); + if (readbackBuffer is null) + { + error = "WebGPU.DeviceCreateBuffer returned null for readback."; + return false; + } + + CommandEncoderDescriptor encoderDescriptor = default; + commandEncoder = api.DeviceCreateCommandEncoder(device, in encoderDescriptor); + if (commandEncoder is null) + { + error = "WebGPU.DeviceCreateCommandEncoder returned null."; + return false; + } + + ImageCopyTexture source = new() + { + Texture = (Texture*)textureHandle, + MipLevel = 0, + Origin = new Origin3D(0, 0, 0), + Aspect = TextureAspect.All + }; + + ImageCopyBuffer destination = new() + { + Buffer = readbackBuffer, + Layout = new TextureDataLayout + { + Offset = 0, + BytesPerRow = (uint)readbackRowBytes, + RowsPerImage = (uint)height + } + }; + + Extent3D copySize = new((uint)width, (uint)height, 1); + api.CommandEncoderCopyTextureToBuffer(commandEncoder, in source, in destination, in copySize); + + CommandBufferDescriptor commandBufferDescriptor = default; + commandBuffer = api.CommandEncoderFinish(commandEncoder, in commandBufferDescriptor); + if (commandBuffer is null) + { + error = "WebGPU.CommandEncoderFinish returned null."; + return false; + } + + api.QueueSubmit(queue, 1, ref commandBuffer); + api.CommandBufferRelease(commandBuffer); + commandBuffer = null; + api.CommandEncoderRelease(commandEncoder); + commandEncoder = null; + + BufferMapAsyncStatus mapStatus = BufferMapAsyncStatus.Unknown; + using ManualResetEventSlim mapReady = new(false); + void Callback(BufferMapAsyncStatus status, void* userData) + { + _ = userData; + mapStatus = status; + mapReady.Set(); + } + + using PfnBufferMapCallback callback = PfnBufferMapCallback.From(Callback); + api.BufferMapAsync(readbackBuffer, MapMode.Read, 0, (nuint)readbackByteCount, callback, null); + if (!WaitForSignal(lease.WgpuExtension, device, mapReady) || mapStatus != BufferMapAsyncStatus.Success) + { + error = $"WebGPU readback map failed with status '{mapStatus}'."; + return false; + } + + void* mapped = api.BufferGetConstMappedRange(readbackBuffer, 0, (nuint)readbackByteCount); + if (mapped is null) + { + api.BufferUnmap(readbackBuffer); + error = "WebGPU.BufferGetConstMappedRange returned null."; + return false; + } + + try + { + ReadOnlySpan readback = new(mapped, checked((int)readbackByteCount)); + byte[] packed = new byte[packedByteCount]; + Span packedSpan = packed; + for (int y = 0; y < height; y++) + { + readback + .Slice(y * readbackRowBytes, packedRowBytes) + .CopyTo(packedSpan.Slice(y * packedRowBytes, packedRowBytes)); + } + + image = Image.LoadPixelData(packed, width, height); + error = string.Empty; + return true; + } + finally + { + api.BufferUnmap(readbackBuffer); + } + } + finally + { + if (commandBuffer is not null) + { + api.CommandBufferRelease(commandBuffer); + } + + if (commandEncoder is not null) + { + api.CommandEncoderRelease(commandEncoder); + } + + if (readbackBuffer is not null) + { + api.BufferRelease(readbackBuffer); + } + } + } + + /// + /// Releases native texture and texture-view handles allocated for tests. + /// + /// The native texture handle. + /// The native texture-view handle. + internal static void Release(nint textureHandle, nint textureViewHandle) + { + if (textureHandle == 0 && textureViewHandle == 0) + { + return; + } + + // Keep the runtime alive while releasing native handles. + using WebGPURuntime.Lease lease = WebGPURuntime.Acquire(); + WebGPU api = lease.Api; + if (textureViewHandle != 0) + { + api.TextureViewRelease((TextureView*)textureViewHandle); + } + + if (textureHandle != 0) + { + api.TextureRelease((Texture*)textureHandle); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Align(int value, int alignment) + => ((value + alignment - 1) / alignment) * alignment; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool WaitForSignal(Wgpu? extension, Device* device, ManualResetEventSlim signal) + { + if (extension is null) + { + return signal.Wait(CallbackTimeoutMilliseconds); + } + + Stopwatch stopwatch = Stopwatch.StartNew(); + while (!signal.IsSet && stopwatch.ElapsedMilliseconds < CallbackTimeoutMilliseconds) + { + _ = extension.DevicePoll(device, true, (WrappedSubmissionIndex*)null); + } + + return signal.IsSet; + } +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUTextureFormatId.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUTextureFormatId.cs new file mode 100644 index 000000000..06cd5e06a --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUTextureFormatId.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Supported WebGPU texture format identifiers used by . +/// +/// +/// Only formats with storage texture binding support are included. +/// Numeric values match the WebGPU WGPUTextureFormat constants. +/// +public enum WebGPUTextureFormatId +{ + /// + /// Four-channel 8-bit normalized unsigned RGBA format. + /// + Rgba8Unorm = 0x12, + + /// + /// Four-channel 8-bit normalized signed format. + /// + Rgba8Snorm = 0x14, + + /// + /// Four-channel 8-bit unsigned integer format. + /// + Rgba8Uint = 0x15, + + /// + /// Four-channel 8-bit normalized unsigned BGRA format. + /// + Bgra8Unorm = 0x17, + + /// + /// Four-channel 16-bit unsigned integer format. + /// + Rgba16Uint = 0x20, + + /// + /// Four-channel 16-bit signed integer format. + /// + Rgba16Sint = 0x21, + + /// + /// Four-channel 16-bit floating-point format. + /// + Rgba16Float = 0x22, + + /// + /// Four-channel 32-bit floating-point format. + /// + Rgba32Float = 0x23, +} diff --git a/src/ImageSharp.Drawing.WebGPU/WebGPUTextureFormatMapper.cs b/src/ImageSharp.Drawing.WebGPU/WebGPUTextureFormatMapper.cs new file mode 100644 index 000000000..21d79f482 --- /dev/null +++ b/src/ImageSharp.Drawing.WebGPU/WebGPUTextureFormatMapper.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Silk.NET.WebGPU; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Maps public WebGPU texture format identifiers to Silk.NET texture formats and back. +/// +internal static class WebGPUTextureFormatMapper +{ + /// + /// Converts a public WebGPU texture format identifier to the corresponding Silk.NET texture format. + /// + /// The public texture format identifier. + /// The matching value. + public static TextureFormat ToSilk(WebGPUTextureFormatId formatId) + => (TextureFormat)(int)formatId; + + /// + /// Converts a Silk.NET texture format to the corresponding public WebGPU texture format identifier. + /// + /// The Silk.NET texture format. + /// The matching value. + public static WebGPUTextureFormatId FromSilk(TextureFormat textureFormat) + => (WebGPUTextureFormatId)(int)textureFormat; +} diff --git a/src/ImageSharp.Drawing/Shapes/ArcLineSegment.cs b/src/ImageSharp.Drawing/ArcLineSegment.cs similarity index 97% rename from src/ImageSharp.Drawing/Shapes/ArcLineSegment.cs rename to src/ImageSharp.Drawing/ArcLineSegment.cs index 994753da2..eed5dafa5 100644 --- a/src/ImageSharp.Drawing/Shapes/ArcLineSegment.cs +++ b/src/ImageSharp.Drawing/ArcLineSegment.cs @@ -80,10 +80,7 @@ public ArcLineSegment(PointF center, SizeF radius, float rotation, float startAn } } - private ArcLineSegment(PointF[] linePoints) - { - this.linePoints = linePoints; - } + private ArcLineSegment(PointF[] linePoints) => this.linePoints = linePoints; /// public PointF EndPoint => this.linePoints[^1]; @@ -96,7 +93,7 @@ private ArcLineSegment(PointF[] linePoints) /// /// The transformation matrix. /// An with the matrix applied to it. - public ILineSegment Transform(Matrix3x2 matrix) + public ILineSegment Transform(Matrix4x4 matrix) { if (matrix.IsIdentity) { @@ -113,7 +110,7 @@ public ILineSegment Transform(Matrix3x2 matrix) } /// - ILineSegment ILineSegment.Transform(Matrix3x2 matrix) => this.Transform(matrix); + ILineSegment ILineSegment.Transform(Matrix4x4 matrix) => this.Transform(matrix); private static PointF[] EllipticArcFromEndParams( PointF from, @@ -203,7 +200,7 @@ private static PointF[] EllipticArcToBezierCurve(Vector2 from, Vector2 center, V prev = p2; } - return points.ToArray(); + return [.. points]; } private static void EndpointToCenterArcParams( diff --git a/src/ImageSharp.Drawing/Shapes/BooleanOperation.cs b/src/ImageSharp.Drawing/BooleanOperation.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/BooleanOperation.cs rename to src/ImageSharp.Drawing/BooleanOperation.cs diff --git a/src/ImageSharp.Drawing/ClipPathExtensions.cs b/src/ImageSharp.Drawing/ClipPathExtensions.cs new file mode 100644 index 000000000..9ad53bbff --- /dev/null +++ b/src/ImageSharp.Drawing/ClipPathExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.PolygonGeometry; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing; + +/// +/// Provides extension methods to that allow the clipping of shapes. +/// +public static class ClipPathExtensions +{ + private static readonly ShapeOptions DefaultOptions = new(); + + /// + /// Clips the specified subject path with the provided clipping paths. + /// + /// The subject path. + /// The clipping paths. + /// The clipped . + public static IPath Clip(this IPath subjectPath, params IPath[] clipPaths) + => subjectPath.Clip(DefaultOptions, clipPaths); + + /// + /// Clips the specified subject path with the provided clipping paths. + /// + /// The subject path. + /// The shape options. + /// The clipping paths. + /// The clipped . + public static IPath Clip( + this IPath subjectPath, + ShapeOptions options, + params IPath[] clipPaths) + => ClippedShapeGenerator.GenerateClippedShapes(options.BooleanOperation, subjectPath, clipPaths); + + /// + /// Clips the specified subject path with the provided clipping paths. + /// + /// The subject path. + /// The clipping paths. + /// The clipped . + public static IPath Clip(this IPath subjectPath, IEnumerable clipPaths) + => subjectPath.Clip(DefaultOptions, clipPaths); + + /// + /// Clips the specified subject path with the provided clipping paths. + /// + /// The subject path. + /// The shape options. + /// The clipping paths. + /// The clipped . + public static IPath Clip( + this IPath subjectPath, + ShapeOptions options, + IEnumerable clipPaths) + => ClippedShapeGenerator.GenerateClippedShapes(options.BooleanOperation, subjectPath, clipPaths); +} diff --git a/src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs b/src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs deleted file mode 100644 index 299ac3338..000000000 --- a/src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing; - -/// -/// Extensions methods fpor the class. -/// -internal static class GraphicsOptionsExtensions -{ - /// - /// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings. - /// - /// The graphics options. - /// The source color. - /// true if the color can be considered opaque - /// - /// Blending and composition is an expensive operation, in some cases, like - /// filling with a solid color, the blending can be avoided by a plain color replacement. - /// This method can be useful for such processors to select the fast path. - /// - public static bool IsOpaqueColorWithoutBlending(this GraphicsOptions options, Color color) - { - if (options.ColorBlendingMode != PixelColorBlendingMode.Normal) - { - return false; - } - - if (options.AlphaCompositionMode is not PixelAlphaCompositionMode.SrcOver and not PixelAlphaCompositionMode.Src) - { - return false; - } - - const float opaque = 1f; - - if (options.BlendPercentage != opaque) - { - return false; - } - - if (color.ToScaledVector4().W != opaque) - { - return false; - } - - return true; - } -} diff --git a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp.Drawing/ComplexPolygon.cs similarity index 99% rename from src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs rename to src/ImageSharp.Drawing/ComplexPolygon.cs index dcdda4067..2e5b75313 100644 --- a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs +++ b/src/ImageSharp.Drawing/ComplexPolygon.cs @@ -67,7 +67,7 @@ public ComplexPolygon(params IPath[] paths) public RectangleF Bounds => this.bounds ??= this.CalcBounds(); /// - public IPath Transform(Matrix3x2 matrix) + public IPath Transform(Matrix4x4 matrix) { if (matrix.IsIdentity) { diff --git a/src/ImageSharp.Drawing/Shapes/CubicBezierLineSegment.cs b/src/ImageSharp.Drawing/CubicBezierLineSegment.cs similarity index 76% rename from src/ImageSharp.Drawing/Shapes/CubicBezierLineSegment.cs rename to src/ImageSharp.Drawing/CubicBezierLineSegment.cs index e9a44bd37..214a0019e 100644 --- a/src/ImageSharp.Drawing/Shapes/CubicBezierLineSegment.cs +++ b/src/ImageSharp.Drawing/CubicBezierLineSegment.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; +using SixLabors.ImageSharp.Drawing.Helpers; namespace SixLabors.ImageSharp.Drawing; @@ -54,7 +55,7 @@ public CubicBezierLineSegment(PointF start, PointF controlPoint1, PointF control /// public CubicBezierLineSegment(PointF start, PointF controlPoint1, PointF controlPoint2, PointF end) - : this(new[] { start, controlPoint1, controlPoint2, end }) + : this([start, controlPoint1, controlPoint2, end]) { } @@ -80,7 +81,7 @@ public CubicBezierLineSegment(PointF start, PointF controlPoint1, PointF control /// /// The matrix. /// A line segment with the matrix applied to it. - public CubicBezierLineSegment Transform(Matrix3x2 matrix) + public CubicBezierLineSegment Transform(Matrix4x4 matrix) { if (matrix.IsIdentity) { @@ -99,7 +100,7 @@ public CubicBezierLineSegment Transform(Matrix3x2 matrix) } /// - ILineSegment ILineSegment.Transform(Matrix3x2 matrix) => this.Transform(matrix); + ILineSegment ILineSegment.Transform(Matrix4x4 matrix) => this.Transform(matrix); private static PointF[] GetDrawingPoints(PointF[] controlPoints) { @@ -108,48 +109,33 @@ private static PointF[] GetDrawingPoints(PointF[] controlPoints) for (int curveIndex = 0; curveIndex < curveCount; curveIndex++) { - List bezierCurveDrawingPoints = FindDrawingPoints(curveIndex, controlPoints); - - if (curveIndex != 0) + if (curveIndex == 0) { - // remove the fist point, as it coincides with the last point of the previous Bezier curve. - bezierCurveDrawingPoints.RemoveAt(0); + drawingPoints.Add(CalculateBezierPoint(curveIndex, 0, controlPoints)); } - drawingPoints.AddRange(bezierCurveDrawingPoints); + SubdivideAndAppend(curveIndex, 0, 1, controlPoints, drawingPoints, 0); + drawingPoints.Add(CalculateBezierPoint(curveIndex, 1, controlPoints)); } - return drawingPoints.ToArray(); + return [.. drawingPoints]; } - private static List FindDrawingPoints(int curveIndex, PointF[] controlPoints) - { - List pointList = []; - - Vector2 left = CalculateBezierPoint(curveIndex, 0, controlPoints); - Vector2 right = CalculateBezierPoint(curveIndex, 1, controlPoints); - - pointList.Add(left); - pointList.Add(right); - - FindDrawingPoints(curveIndex, 0, 1, pointList, 1, controlPoints, 0); - - return pointList; - } - - private static int FindDrawingPoints( + /// + /// Recursively subdivides a cubic bezier curve segment and appends points in left-to-right order. + /// Points are appended (not inserted), avoiding O(n) shifts per point. + /// + private static void SubdivideAndAppend( int curveIndex, float t0, float t1, - List pointList, - int insertionIndex, PointF[] controlPoints, + List output, int depth) { - // max recursive depth for control points, means this is approx the max number of points discoverable if (depth > 999) { - return 0; + return; } Vector2 left = CalculateBezierPoint(curveIndex, t0, controlPoints); @@ -157,7 +143,7 @@ private static int FindDrawingPoints( if ((left - right).LengthSquared() < MinimumSqrDistance) { - return 0; + return; } float midT = (t0 + t1) / 2; @@ -168,17 +154,11 @@ private static int FindDrawingPoints( if (Vector2.Dot(leftDirection, rightDirection) > DivisionThreshold || Math.Abs(midT - 0.5f) < 0.0001f) { - int pointsAddedCount = 0; - - pointsAddedCount += FindDrawingPoints(curveIndex, t0, midT, pointList, insertionIndex, controlPoints, depth + 1); - pointList.Insert(insertionIndex + pointsAddedCount, mid); - pointsAddedCount++; - pointsAddedCount += FindDrawingPoints(curveIndex, midT, t1, pointList, insertionIndex + pointsAddedCount, controlPoints, depth + 1); - - return pointsAddedCount; + // Recurse left half, emit midpoint, recurse right half — all in order. + SubdivideAndAppend(curveIndex, t0, midT, controlPoints, output, depth + 1); + output.Add(mid); + SubdivideAndAppend(curveIndex, midT, t1, controlPoints, output, depth + 1); } - - return 0; } private static PointF CalculateBezierPoint(int curveIndex, float t, PointF[] controlPoints) diff --git a/src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs b/src/ImageSharp.Drawing/EllipsePolygon.cs similarity index 98% rename from src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs rename to src/ImageSharp.Drawing/EllipsePolygon.cs index 6b9cac56f..4456c4bf5 100644 --- a/src/ImageSharp.Drawing/Shapes/EllipsePolygon.cs +++ b/src/ImageSharp.Drawing/EllipsePolygon.cs @@ -59,7 +59,7 @@ public EllipsePolygon(float x, float y, float radius) } /// - public override IPath Transform(Matrix3x2 matrix) + public override IPath Transform(Matrix4x4 matrix) { if (matrix.IsIdentity) { diff --git a/src/ImageSharp.Drawing/Shapes/EmptyPath.cs b/src/ImageSharp.Drawing/EmptyPath.cs similarity index 94% rename from src/ImageSharp.Drawing/Shapes/EmptyPath.cs rename to src/ImageSharp.Drawing/EmptyPath.cs index 6789db399..ef39f221c 100644 --- a/src/ImageSharp.Drawing/Shapes/EmptyPath.cs +++ b/src/ImageSharp.Drawing/EmptyPath.cs @@ -35,5 +35,5 @@ public sealed class EmptyPath : IPath public IEnumerable Flatten() => []; /// - public IPath Transform(Matrix3x2 matrix) => this; + public IPath Transform(Matrix4x4 matrix) => this; } diff --git a/src/ImageSharp.Drawing/Helpers/ArrayExtensions.cs b/src/ImageSharp.Drawing/Helpers/ArrayExtensions.cs new file mode 100644 index 000000000..33a658455 --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/ArrayExtensions.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Helpers; + +/// +/// Extension methods for arrays. +/// +internal static class ArrayExtensions +{ + /// + /// Merges two arrays into one. + /// + /// the type of the array + /// The first source array. + /// The second source array. + /// + /// A new array containing the elements of both source arrays. + /// + public static T[] Merge(this T[] source1, T[] source2) + { + if (source2 is null || source2.Length == 0) + { + return source1; + } + + T[] target = new T[source1.Length + source2.Length]; + + for (int i = 0; i < source1.Length; i++) + { + target[i] = source1[i]; + } + + for (int i = 0; i < source2.Length; i++) + { + target[i + source1.Length] = source2[i]; + } + + return target; + } +} diff --git a/src/ImageSharp.Drawing/Helpers/MatrixUtilities.cs b/src/ImageSharp.Drawing/Helpers/MatrixUtilities.cs new file mode 100644 index 000000000..6d0168044 --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/MatrixUtilities.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Drawing.Helpers; + +/// +/// Provides helper methods for extracting properties from transformation matrices. +/// +internal static class MatrixUtilities +{ + /// + /// Extracts the average 2D scale factor from a . + /// This is the mean of the X and Y axis scale magnitudes, suitable for + /// uniformly scaling radii under non-uniform or projective transforms. + /// + /// The transformation matrix. + /// The average scale factor. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetAverageScale(in Matrix4x4 matrix) + { + float sx = MathF.Sqrt((matrix.M11 * matrix.M11) + (matrix.M12 * matrix.M12)); + float sy = MathF.Sqrt((matrix.M21 * matrix.M21) + (matrix.M22 * matrix.M22)); + return (sx + sy) * 0.5f; + } +} diff --git a/src/ImageSharp.Drawing/Helpers/PolygonUtilities.cs b/src/ImageSharp.Drawing/Helpers/PolygonUtilities.cs new file mode 100644 index 000000000..e035fc940 --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/PolygonUtilities.cs @@ -0,0 +1,125 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing; + +namespace SixLabors.ImageSharp.Drawing.Helpers; + +/// +/// Provides low-level geometry helpers for polygon winding and segment intersection. +/// +/// +/// Polygon methods expect a closed ring where the first point is repeated as the last point. +/// Orientation signs are defined using world-space math conventions (Y points up): +/// positive signed area is counter-clockwise and negative signed area is clockwise. +/// In screen space (Y points down), the visual winding appears inverted. +/// +internal static class PolygonUtilities +{ + // Epsilon used for floating-point tolerance. Values within +-Eps are treated as zero. + // This reduces instability when segments are nearly parallel or endpoints are close. + private const float Eps = 1e-3f; + private const float MinusEps = -Eps; + private const float OnePlusEps = 1 + Eps; + + /// + /// Ensures that a closed polygon ring matches the expected orientation. + /// + /// Polygon ring to normalize in place. + /// + /// Expected orientation sign: + /// positive for counter-clockwise in world space, negative for clockwise in world space. + /// + /// + /// The ring is reversed only when its orientation sign disagrees with + /// . Degenerate rings (zero area) are not changed. + /// + public static void EnsureOrientation(Span polygon, int expectedOrientation) + { + if (GetPolygonOrientation(polygon) * expectedOrientation < 0) + { + polygon.Reverse(); + } + } + + /// + /// Returns the orientation sign of a closed polygon ring using the shoelace sum. + /// + /// Closed polygon ring. + /// + /// -1 for clockwise, 1 for counter-clockwise, or 0 for degenerate (zero-area) input. + /// + private static int GetPolygonOrientation(ReadOnlySpan polygon) + { + float sum = 0f; + for (int i = 0; i < polygon.Length - 1; ++i) + { + PointF current = polygon[i]; + PointF next = polygon[i + 1]; + sum += (current.X * next.Y) - (next.X * current.Y); + } + + // A tolerant compare could be used here, but edge scanning does not special-case + // zero-area or near-zero-area input, so we keep this strict sign check. + return Math.Sign(sum); + } + + /// + /// Tests whether two line segments intersect, excluding collinear overlap cases. + /// + /// Start point of segment A. + /// End point of segment A. + /// Start point of segment B. + /// End point of segment B. + /// + /// Receives the intersection point when an intersection is found. + /// If no intersection is detected, the value is not modified. + /// + /// + /// when the segments intersect within their extents + /// (including endpoints); otherwise . + /// + /// + /// This solves the two segment equations in parametric form and accepts values in [0, 1] + /// with an epsilon margin for floating-point tolerance. + /// Parallel and collinear pairs are rejected early (cross product ~= 0). + /// + public static bool LineSegmentToLineSegmentIgnoreCollinear( + Vector2 a0, + Vector2 a1, + Vector2 b0, + Vector2 b1, + ref Vector2 intersectionPoint) + { + // Direction vectors of the segments. + float dax = a1.X - a0.X; + float day = a1.Y - a0.Y; + float dbx = b1.X - b0.X; + float dby = b1.Y - b0.Y; + + // Cross product of the direction vectors. Near zero means parallel/collinear. + float crossD = (-dbx * day) + (dax * dby); + + // Reject parallel and collinear lines. Collinear overlap is intentionally not handled. + if (crossD is > MinusEps and < Eps) + { + return false; + } + + // Solve for parameters s and t where: + // a0 + t * (a1 - a0) = b0 + s * (b1 - b0) + float s = ((-day * (a0.X - b0.X)) + (dax * (a0.Y - b0.Y))) / crossD; + float t = ((dbx * (a0.Y - b0.Y)) - (dby * (a0.X - b0.X))) / crossD; + + // If both parameters are within [0,1] (with tolerance), the segments intersect. + if (s > MinusEps && s < OnePlusEps && t > MinusEps && t < OnePlusEps) + { + intersectionPoint.X = a0.X + (t * dax); + intersectionPoint.Y = a0.Y + (t * day); + return true; + } + + return false; + } +} diff --git a/src/ImageSharp.Drawing/Helpers/ThreadLocalBlenderBuffers.cs b/src/ImageSharp.Drawing/Helpers/ThreadLocalBlenderBuffers.cs new file mode 100644 index 000000000..1468843eb --- /dev/null +++ b/src/ImageSharp.Drawing/Helpers/ThreadLocalBlenderBuffers.cs @@ -0,0 +1,124 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Drawing.Helpers; + +/// +/// Provides per-thread scratch buffers used by brush applicators during blending. +/// +/// The target pixel type. +/// +/// +/// Each participating thread gets its own pair of scanline-sized buffers: +/// one for blend amounts ( values) and, optionally, one for overlay pixels. +/// +/// +/// This avoids per-row allocations while preventing cross-thread contention on shared buffers. +/// +/// +/// Instances must be disposed to release all thread-local allocations. +/// +/// +internal class ThreadLocalBlenderBuffers : IDisposable + where TPixel : unmanaged, IPixel +{ + private readonly ThreadLocal data; + + /// + /// Initializes a new instance of the class. + /// + /// The allocator used to create per-thread buffers. + /// The required buffer length, in pixels. + /// + /// to allocate only the amount buffer. + /// Use this when blending does not require an intermediate overlay color buffer. + /// + public ThreadLocalBlenderBuffers(MemoryAllocator allocator, int scanlineWidth, bool amountBufferOnly = false) + => this.data = new ThreadLocal(() => new BufferOwner(allocator, scanlineWidth, amountBufferOnly), true); + + /// + /// Gets the current thread's amount buffer. + /// + /// + /// The span length is equal to the configured scanline width. + /// The returned span is thread-local and should only be used on the calling thread. + /// + public Span AmountSpan => this.data.Value!.AmountSpan; + + /// + /// Gets the current thread's overlay color buffer. + /// + /// + /// When the instance was created with amountBufferOnly=true, + /// this property returns an empty span. + /// + public Span OverlaySpan => this.data.Value!.OverlaySpan; + + /// + public void Dispose() + { + foreach (BufferOwner d in this.data.Values) + { + d.Dispose(); + } + + this.data.Dispose(); + } + + /// + /// Owns the actual memory buffers for a single thread. + /// + private sealed class BufferOwner : IDisposable + { + private readonly IMemoryOwner amountBuffer; + private readonly IMemoryOwner? overlayBuffer; + + /// + /// Initializes a new instance of the class. + /// + /// The allocator used for memory ownership. + /// The required buffer length, in pixels. + /// + /// to omit overlay buffer allocation. + /// + public BufferOwner(MemoryAllocator allocator, int scanlineLength, bool amountBufferOnly) + { + this.amountBuffer = allocator.Allocate(scanlineLength); + this.overlayBuffer = amountBufferOnly ? null : allocator.Allocate(scanlineLength); + } + + /// + /// Gets the per-thread amount buffer. + /// + public Span AmountSpan => this.amountBuffer.Memory.Span; + + /// + /// Gets the per-thread overlay buffer. + /// + /// + /// Returns an empty span when overlay storage was intentionally not allocated. + /// + public Span OverlaySpan + { + get + { + if (this.overlayBuffer != null) + { + return this.overlayBuffer.Memory.Span; + } + + return []; + } + } + + /// + public void Dispose() + { + this.amountBuffer.Dispose(); + this.overlayBuffer?.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Shapes/IInternalPathOwner.cs b/src/ImageSharp.Drawing/IInternalPathOwner.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/IInternalPathOwner.cs rename to src/ImageSharp.Drawing/IInternalPathOwner.cs diff --git a/src/ImageSharp.Drawing/Shapes/ILineSegment.cs b/src/ImageSharp.Drawing/ILineSegment.cs similarity index 95% rename from src/ImageSharp.Drawing/Shapes/ILineSegment.cs rename to src/ImageSharp.Drawing/ILineSegment.cs index 19d485e7d..9cd9dee7f 100644 --- a/src/ImageSharp.Drawing/Shapes/ILineSegment.cs +++ b/src/ImageSharp.Drawing/ILineSegment.cs @@ -29,5 +29,5 @@ public interface ILineSegment /// /// The matrix. /// A line segment with the matrix applied to it. - ILineSegment Transform(Matrix3x2 matrix); + ILineSegment Transform(Matrix4x4 matrix); } diff --git a/src/ImageSharp.Drawing/Shapes/IPath.cs b/src/ImageSharp.Drawing/IPath.cs similarity index 96% rename from src/ImageSharp.Drawing/Shapes/IPath.cs rename to src/ImageSharp.Drawing/IPath.cs index 4e8be5840..bd305e38e 100644 --- a/src/ImageSharp.Drawing/Shapes/IPath.cs +++ b/src/ImageSharp.Drawing/IPath.cs @@ -31,7 +31,7 @@ public interface IPath /// /// The matrix. /// A new path with the matrix applied to it. - public IPath Transform(Matrix3x2 matrix); + public IPath Transform(Matrix4x4 matrix); /// /// Returns this path with all figures closed. diff --git a/src/ImageSharp.Drawing/Shapes/IPathCollection.cs b/src/ImageSharp.Drawing/IPathCollection.cs similarity index 91% rename from src/ImageSharp.Drawing/Shapes/IPathCollection.cs rename to src/ImageSharp.Drawing/IPathCollection.cs index ef2834721..5d2780235 100644 --- a/src/ImageSharp.Drawing/Shapes/IPathCollection.cs +++ b/src/ImageSharp.Drawing/IPathCollection.cs @@ -20,5 +20,5 @@ public interface IPathCollection : IEnumerable /// /// The matrix. /// A new path collection with the matrix applied to it. - public IPathCollection Transform(Matrix3x2 matrix); + public IPathCollection Transform(Matrix4x4 matrix); } diff --git a/src/ImageSharp.Drawing/Shapes/IPathInternals.cs b/src/ImageSharp.Drawing/IPathInternals.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/IPathInternals.cs rename to src/ImageSharp.Drawing/IPathInternals.cs diff --git a/src/ImageSharp.Drawing/Shapes/ISimplePath.cs b/src/ImageSharp.Drawing/ISimplePath.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/ISimplePath.cs rename to src/ImageSharp.Drawing/ISimplePath.cs diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index 153c102b7..8d508564b 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -13,6 +13,7 @@ An extension to ImageSharp that allows the drawing of images, paths, and text. Debug;Release true + @@ -25,7 +26,7 @@ enable Nullable - + @@ -38,15 +39,17 @@ - + - - - + + + + + diff --git a/src/ImageSharp.Drawing/Shapes/InnerJoin.cs b/src/ImageSharp.Drawing/InnerJoin.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/InnerJoin.cs rename to src/ImageSharp.Drawing/InnerJoin.cs diff --git a/src/ImageSharp.Drawing/InternalPath.cs b/src/ImageSharp.Drawing/InternalPath.cs new file mode 100644 index 000000000..e7571a5ce --- /dev/null +++ b/src/ImageSharp.Drawing/InternalPath.cs @@ -0,0 +1,389 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Drawing; + +/// +/// Internal logic for integrating linear paths. +/// +internal class InternalPath +{ + /// + /// The epsilon for float comparison + /// + private const float Epsilon = 0.003f; + private const float Epsilon2 = 0.2f; + + /// + /// The points. + /// + private readonly PointData[] points; + + /// + /// The closed path. + /// + private readonly bool closedPath; + + /// + /// Initializes a new instance of the class. + /// + /// The segments. + /// if set to true [is closed path]. + /// Whether to remove close and collinear vertices + internal InternalPath(IReadOnlyList segments, bool isClosedPath, bool removeCloseAndCollinear = true) + : this(Simplify(segments, isClosedPath, removeCloseAndCollinear), isClosedPath) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The segment. + /// if set to true [is closed path]. + internal InternalPath(ILineSegment segment, bool isClosedPath) + : this(segment?.Flatten() ?? Array.Empty(), isClosedPath) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The points. + /// if set to true [is closed path]. + internal InternalPath(ReadOnlyMemory points, bool isClosedPath) + : this(Simplify(points.Span, isClosedPath, true), isClosedPath) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The points. + /// if set to true [is closed path]. + private InternalPath(PointData[] points, bool isClosedPath) + { + this.points = points; + this.closedPath = isClosedPath; + + if (this.points.Length > 0) + { + float minX, minY, maxX, maxY, length; + length = 0; + minX = minY = float.MaxValue; + maxX = maxY = float.MinValue; + + foreach (PointData point in this.points) + { + length += point.Length; + minX = Math.Min(point.Point.X, minX); + minY = Math.Min(point.Point.Y, minY); + maxX = Math.Max(point.Point.X, maxX); + maxY = Math.Max(point.Point.Y, maxY); + } + + this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY); + this.Length = length; + } + else + { + this.Bounds = RectangleF.Empty; + this.Length = 0; + } + } + + /// + /// Gets the bounds. + /// + /// + /// The bounds. + /// + public RectangleF Bounds { get; } + + /// + /// Gets the length. + /// + /// + /// The length. + /// + public float Length { get; } + + /// + /// Gets the length. + /// + public int PointCount => this.points.Length; + + /// + /// Gets the points. + /// + /// The + internal ReadOnlyMemory Points() => this.points.Select(x => x.Point).ToArray(); + + /// + /// Calculates the point a certain distance a path. + /// + /// The distance along the path to find details of. + /// + /// Returns details about a point along a path. + /// + /// Thrown if no points found. + internal SegmentInfo PointAlongPath(float distanceAlongPath) + { + int pointCount = this.PointCount; + if (this.closedPath) + { + // Move the distance back to the beginning since this is a closed polygon. + distanceAlongPath %= this.Length; + pointCount--; + } + + for (int i = 0; i < pointCount; i++) + { + int next = WrapArrayIndex(i + 1, this.PointCount); + if (distanceAlongPath < this.points[next].Length) + { + float t = distanceAlongPath / this.points[next].Length; + Vector2 point = Vector2.Lerp(this.points[i].Point, this.points[next].Point, t); + Vector2 diff = this.points[i].Point - this.points[next].Point; + + return new SegmentInfo + { + Point = point, + Angle = (float)(Math.Atan2(diff.Y, diff.X) % (Math.PI * 2)) + }; + } + + distanceAlongPath -= this.points[next].Length; + } + + // Closed paths will never reach this point. + // For open paths we're going to create a new virtual point that extends past the path. + // The position and angle for that point are calculated based upon the last two points. + PointF a = this.points[Math.Max(this.points.Length - 2, 0)].Point; + PointF b = this.points[^1].Point; + Vector2 delta = a - b; + float angle = (float)(Math.Atan2(delta.Y, delta.X) % (Math.PI * 2)); + + Matrix4x4 transform = Matrix4x4.CreateRotationZ(angle - MathF.PI) * Matrix4x4.CreateTranslation(b.X, b.Y, 0); + + return new SegmentInfo + { + Point = PointF.Transform(new PointF(distanceAlongPath, 0), transform), + Angle = angle + }; + } + + // Modulo is a very slow operation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int WrapArrayIndex(int i, int arrayLength) => i < arrayLength ? i : i - arrayLength; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static PointOrientation CalculateOrientation(Vector2 p, Vector2 q, Vector2 r) + { + // See http://www.geeksforgeeks.org/orientation-3-ordered-points/ + // for details of below formula. + Vector2 qp = q - p; + Vector2 rq = r - q; + float val = (qp.Y * rq.X) - (qp.X * rq.Y); + + if (val is > -Epsilon and < Epsilon) + { + return PointOrientation.Collinear; // colinear + } + + return (val > 0) ? PointOrientation.Clockwise : PointOrientation.Counterclockwise; // clock or counterclock wise + } + + /// + /// Simplifies the collection of segments. + /// + /// The segments. + /// Weather the path is closed or open. + /// Whether to remove close and collinear vertices + /// + /// The . + /// + private static PointData[] Simplify(IReadOnlyList segments, bool isClosed, bool removeCloseAndCollinear) + { + // Pre-compute capacity from cached flattened lengths to avoid List resizing. + int totalPoints = 0; + for (int s = 0; s < segments.Count; s++) + { + totalPoints += segments[s].Flatten().Length; + } + + List simplified = new(totalPoints); + + // Track indices where collinear direction reversals represent user-intended + // geometry: interior points of multi-point linear segments, and junction + // points between two linear segments (e.g. PathBuilder LineTo → LineTo). + // Reversals at all other indices (flattened curves, curve junctions) are + // artifacts and should be removed normally. + HashSet? linearReversalIndices = null; + ILineSegment? prevSeg = null; + + foreach (ILineSegment seg in segments) + { + int start = simplified.Count; + ReadOnlyMemory points = seg.Flatten(); + simplified.AddRange(points.Span); + + if (seg is LinearLineSegment) + { + // Interior points of a multi-point linear segment (e.g. DrawLine with 3+ points). + if (points.Length > 2) + { + linearReversalIndices ??= []; + for (int i = start + 1; i < start + points.Length - 1; i++) + { + _ = linearReversalIndices.Add(i); + } + } + + // Junction between two linear segments (e.g. PathBuilder LineTo → LineTo). + if (prevSeg is LinearLineSegment && start > 0) + { + linearReversalIndices ??= []; + _ = linearReversalIndices.Add(start); + } + } + + prevSeg = seg; + } + + return Simplify(CollectionsMarshal.AsSpan(simplified), isClosed, removeCloseAndCollinear, linearReversalIndices); + } + + private static PointData[] Simplify(ReadOnlySpan points, bool isClosed, bool removeCloseAndCollinear, HashSet? linearReversalIndices = null) + { + int polyCorners = points.Length; + if (polyCorners == 0) + { + return []; + } + + List results = new(polyCorners); + Vector2 lastPoint = points[0]; + + if (!isClosed) + { + results.Add(new PointData + { + Point = points[0], + Orientation = PointOrientation.Collinear, + Length = 0 + }); + } + else + { + int prev = polyCorners; + do + { + prev--; + if (prev == 0) + { + // All points are common, shouldn't match anything + results.Add( + new PointData + { + Point = points[0], + Orientation = PointOrientation.Collinear, + Length = 0, + }); + + return [.. results]; + } + } + while (removeCloseAndCollinear && Equivalent(points[0], points[prev], Epsilon2)); // skip points too close together + + polyCorners = prev + 1; + lastPoint = points[prev]; + + results.Add( + new PointData + { + Point = points[0], + Orientation = CalculateOrientation(lastPoint, points[0], points[1]), + Length = Vector2.Distance(lastPoint, points[0]), + }); + + lastPoint = points[0]; + } + + for (int i = 1; i < polyCorners; i++) + { + int next = WrapArrayIndex(i + 1, polyCorners); + PointOrientation or = CalculateOrientation(lastPoint, points[i], points[next]); + if (removeCloseAndCollinear && or == PointOrientation.Collinear && next != 0) + { + // Preserve collinear points that represent a direction reversal (U-turn) + // within a single segment. E.g. (10,10)→(90,10)→(20,10): the middle point + // is collinear but the stroker needs to see the reversal. + // Don't preserve reversals at segment boundaries — these arise from joining + // different path segments (e.g. arc-to-arc) and are not user-intended. + bool preserve = false; + if (linearReversalIndices == null || linearReversalIndices.Contains(i)) + { + Vector2 incoming = (Vector2)points[i] - lastPoint; + Vector2 outgoing = (Vector2)points[next] - (Vector2)points[i]; + float inLen = incoming.Length(); + float outLen = outgoing.Length(); + preserve = inLen > Epsilon && outLen > Epsilon && Vector2.Dot(incoming, outgoing) < 0; + } + + if (!preserve) + { + continue; + } + } + + results.Add( + new PointData + { + Point = points[i], + Orientation = or, + Length = Vector2.Distance(lastPoint, points[i]), + }); + lastPoint = points[i]; + } + + if (isClosed && removeCloseAndCollinear) + { + // walk back removing collinear points + while (results.Count > 2 && results[^1].Orientation == PointOrientation.Collinear) + { + results.RemoveAt(results.Count - 1); + } + } + + return [.. results]; + } + + /// + /// Merges the specified source2. + /// + /// The source1. + /// The source2. + /// The threshold. + /// + /// the Merged arrays + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool Equivalent(PointF source1, PointF source2, float threshold) + { + Vector2 abs = Vector2.Abs(source1 - source2); + return abs.X < threshold && abs.Y < threshold; + } + + private struct PointData + { + public PointF Point; + public PointOrientation Orientation; + public float Length; + } +} diff --git a/src/ImageSharp.Drawing/Shapes/IntersectionRule.cs b/src/ImageSharp.Drawing/IntersectionRule.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/IntersectionRule.cs rename to src/ImageSharp.Drawing/IntersectionRule.cs diff --git a/src/ImageSharp.Drawing/Shapes/LineCap.cs b/src/ImageSharp.Drawing/LineCap.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/LineCap.cs rename to src/ImageSharp.Drawing/LineCap.cs diff --git a/src/ImageSharp.Drawing/Shapes/LineJoin.cs b/src/ImageSharp.Drawing/LineJoin.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/LineJoin.cs rename to src/ImageSharp.Drawing/LineJoin.cs diff --git a/src/ImageSharp.Drawing/Shapes/LinearLineSegment.cs b/src/ImageSharp.Drawing/LinearLineSegment.cs similarity index 94% rename from src/ImageSharp.Drawing/Shapes/LinearLineSegment.cs rename to src/ImageSharp.Drawing/LinearLineSegment.cs index f1170baf3..f410b7063 100644 --- a/src/ImageSharp.Drawing/Shapes/LinearLineSegment.cs +++ b/src/ImageSharp.Drawing/LinearLineSegment.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; +using SixLabors.ImageSharp.Drawing.Helpers; namespace SixLabors.ImageSharp.Drawing; @@ -71,7 +72,7 @@ public LinearLineSegment(PointF[] points) /// /// A line segment with the matrix applied to it. /// - public LinearLineSegment Transform(Matrix3x2 matrix) + public LinearLineSegment Transform(Matrix4x4 matrix) { if (matrix.IsIdentity) { @@ -94,5 +95,5 @@ public LinearLineSegment Transform(Matrix3x2 matrix) /// /// The matrix. /// A line segment with the matrix applied to it. - ILineSegment ILineSegment.Transform(Matrix3x2 matrix) => this.Transform(matrix); + ILineSegment ILineSegment.Transform(Matrix4x4 matrix) => this.Transform(matrix); } diff --git a/src/ImageSharp.Drawing/OutlinePathExtensions.cs b/src/ImageSharp.Drawing/OutlinePathExtensions.cs new file mode 100644 index 000000000..77f40996f --- /dev/null +++ b/src/ImageSharp.Drawing/OutlinePathExtensions.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.PolygonGeometry; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing; + +/// +/// Extensions to that allow the generation of outlines. +/// +public static class OutlinePathExtensions +{ + private static readonly StrokeOptions DefaultOptions = new(); + + /// + /// Generates an outline of the path. + /// + /// The path to outline + /// The outline width. + /// A new representing the outline. + public static IPath GenerateOutline(this IPath path, float width) + => GenerateOutline(path, width, DefaultOptions); + + /// + /// Generates an outline of the path. + /// + /// The path to outline + /// The outline width. + /// The stroke geometry options. + /// A new representing the outline. + public static IPath GenerateOutline(this IPath path, float width, StrokeOptions strokeOptions) + { + if (width <= 0) + { + return Path.Empty; + } + + return StrokedShapeGenerator.GenerateStrokedShapes(path, width, strokeOptions); + } + + /// + /// Generates an outline of the path with alternating on and off segments based on the pattern. + /// + /// The path to outline + /// The outline width. + /// The pattern made of multiples of the width. + /// A new representing the outline. + public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan pattern) + => path.GenerateOutline(width, pattern, false); + + /// + /// Generates an outline of the path with alternating on and off segments based on the pattern. + /// + /// The path to outline + /// The outline width. + /// The pattern made of multiples of the width. + /// The stroke geometry options. + /// A new representing the outline. + public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan pattern, StrokeOptions strokeOptions) + => GenerateOutline(path, width, pattern, false, strokeOptions); + + /// + /// Generates an outline of the path with alternating on and off segments based on the pattern. + /// + /// The path to outline + /// The outline width. + /// The pattern made of multiples of the width. + /// Whether the first item in the pattern is on or off. + /// A new representing the outline. + public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan pattern, bool startOff) + => GenerateOutline(path, width, pattern, startOff, DefaultOptions); + + /// + /// Generates an outline of the path with alternating on and off segments based on the pattern. + /// + /// The path to outline + /// The outline width. + /// The pattern made of multiples of the width. + /// Whether the first item in the pattern is on or off. + /// The stroke geometry options. + /// A new representing the outline. + public static IPath GenerateOutline( + this IPath path, + float width, + ReadOnlySpan pattern, + bool startOff, + StrokeOptions strokeOptions) + { + if (width <= 0) + { + return Path.Empty; + } + + if (pattern.Length < 2) + { + return path.GenerateOutline(width, strokeOptions); + } + + IPath dashed = path.GenerateDashes(width, pattern, startOff); + + // GenerateDashes returns the original path when the pattern is degenerate + // or when segmentation would exceed safety limits; stroke it as solid. + if (ReferenceEquals(dashed, path)) + { + return path.GenerateOutline(width, strokeOptions); + } + + if (dashed == Path.Empty) + { + return Path.Empty; + } + + // Each dash segment is an open sub-path; stroke expansion and boolean merge + // are handled by the generator. + return StrokedShapeGenerator.GenerateStrokedShapes(dashed, width, strokeOptions); + } +} diff --git a/src/ImageSharp.Drawing/Shapes/Path.cs b/src/ImageSharp.Drawing/Path.cs similarity index 99% rename from src/ImageSharp.Drawing/Shapes/Path.cs rename to src/ImageSharp.Drawing/Path.cs index 4ddf0c421..803483226 100644 --- a/src/ImageSharp.Drawing/Shapes/Path.cs +++ b/src/ImageSharp.Drawing/Path.cs @@ -93,7 +93,7 @@ public Path(params ILineSegment[] segments) this.innerPath ??= new InternalPath(this.lineSegments, this.IsClosed, this.RemoveCloseAndCollinearPoints); /// - public virtual IPath Transform(Matrix3x2 matrix) + public virtual IPath Transform(Matrix4x4 matrix) { if (matrix.IsIdentity) { diff --git a/src/ImageSharp.Drawing/Shapes/PathBuilder.cs b/src/ImageSharp.Drawing/PathBuilder.cs similarity index 95% rename from src/ImageSharp.Drawing/Shapes/PathBuilder.cs rename to src/ImageSharp.Drawing/PathBuilder.cs index c29510e4f..f18b0d92b 100644 --- a/src/ImageSharp.Drawing/Shapes/PathBuilder.cs +++ b/src/ImageSharp.Drawing/PathBuilder.cs @@ -12,17 +12,17 @@ namespace SixLabors.ImageSharp.Drawing; public class PathBuilder { private readonly List
figures = []; - private readonly Matrix3x2 defaultTransform; + private readonly Matrix4x4 defaultTransform; private Figure currentFigure; - private Matrix3x2 currentTransform; - private Matrix3x2 setTransform; + private Matrix4x4 currentTransform; + private Matrix4x4 setTransform; private Vector2 currentPoint; /// /// Initializes a new instance of the class. /// public PathBuilder() - : this(Matrix3x2.Identity) + : this(Matrix4x4.Identity) { } @@ -30,7 +30,7 @@ public PathBuilder() /// Initializes a new instance of the class. /// /// The default transform. - public PathBuilder(Matrix3x2 defaultTransform) + public PathBuilder(Matrix4x4 defaultTransform) { this.defaultTransform = defaultTransform; this.Clear(); @@ -41,19 +41,19 @@ public PathBuilder(Matrix3x2 defaultTransform) /// Gets the current transformation matrix. /// /// - /// Returns a copy of the matrix. Because is a value type, + /// Returns a copy of the matrix. Because is a value type, /// modifications to the returned value do not affect the internal state. To change the transform, - /// call . + /// call . /// /// The current transformation matrix. - public Matrix3x2 Transform => this.currentTransform; + public Matrix4x4 Transform => this.currentTransform; /// /// Sets the translation to be applied to all items to follow being applied to the . /// /// The transform. /// The . - public PathBuilder SetTransform(Matrix3x2 transform) + public PathBuilder SetTransform(Matrix4x4 transform) { this.setTransform = transform; this.currentTransform = this.setTransform * this.defaultTransform; @@ -68,7 +68,7 @@ public PathBuilder SetTransform(Matrix3x2 transform) public PathBuilder SetOrigin(PointF origin) { // The new origin should be transformed based on the default transform - this.setTransform.Translation = origin; + this.setTransform.Translation = new Vector3(origin.X, origin.Y, 0); this.currentTransform = this.setTransform * this.defaultTransform; return this; @@ -80,7 +80,7 @@ public PathBuilder SetOrigin(PointF origin) /// The . public PathBuilder ResetTransform() { - this.setTransform = Matrix3x2.Identity; + this.setTransform = Matrix4x4.Identity; this.currentTransform = this.setTransform * this.defaultTransform; return this; @@ -92,7 +92,7 @@ public PathBuilder ResetTransform() /// The . public PathBuilder ResetOrigin() { - this.setTransform.Translation = Vector2.Zero; + this.setTransform.Translation = Vector3.Zero; this.currentTransform = this.setTransform * this.defaultTransform; return this; @@ -110,6 +110,15 @@ public PathBuilder MoveTo(PointF point) return this; } + /// + /// Moves to current point to the supplied vector. + /// + /// The x-coordinate. + /// The y-coordinate. + /// The + public PathBuilder MoveTo(float x, float y) + => this.MoveTo(new PointF(x, y)); + /// /// Draws the line connecting the current the current point to the new point. /// @@ -456,7 +465,7 @@ public PathBuilder Reset() /// /// Clears all drawn paths, Leaving any applied transforms. /// - [MemberNotNull(nameof(this.currentFigure))] + [MemberNotNull(nameof(currentFigure))] public void Clear() { this.currentFigure = new Figure(); @@ -476,7 +485,7 @@ private class Figure public IPath Build() => this.IsClosed - ? new Polygon(this.segments.ToArray(), true) + ? new Polygon([.. this.segments], true) : new Path(this.segments.ToArray()); } } diff --git a/src/ImageSharp.Drawing/Shapes/PathCollection.cs b/src/ImageSharp.Drawing/PathCollection.cs similarity index 97% rename from src/ImageSharp.Drawing/Shapes/PathCollection.cs rename to src/ImageSharp.Drawing/PathCollection.cs index 9ae4bc739..2da4c7f81 100644 --- a/src/ImageSharp.Drawing/Shapes/PathCollection.cs +++ b/src/ImageSharp.Drawing/PathCollection.cs @@ -63,7 +63,7 @@ private RectangleF CalcBounds() public IEnumerator GetEnumerator() => ((IEnumerable)this.paths).GetEnumerator(); /// - public IPathCollection Transform(Matrix3x2 matrix) + public IPathCollection Transform(Matrix4x4 matrix) { IPath[] result = new IPath[this.paths.Length]; diff --git a/src/ImageSharp.Drawing/Shapes/PathExtensions.Internal.cs b/src/ImageSharp.Drawing/PathExtensions.Internal.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/PathExtensions.Internal.cs rename to src/ImageSharp.Drawing/PathExtensions.Internal.cs diff --git a/src/ImageSharp.Drawing/Shapes/PathExtensions.cs b/src/ImageSharp.Drawing/PathExtensions.cs similarity index 91% rename from src/ImageSharp.Drawing/Shapes/PathExtensions.cs rename to src/ImageSharp.Drawing/PathExtensions.cs index a2cffd2c5..a0dcd38ca 100644 --- a/src/ImageSharp.Drawing/Shapes/PathExtensions.cs +++ b/src/ImageSharp.Drawing/PathExtensions.cs @@ -17,7 +17,7 @@ public static partial class PathExtensions /// The radians to rotate the path. /// A with a rotate transform applied. public static IPathCollection Rotate(this IPathCollection path, float radians) - => path.Transform(Matrix3x2Extensions.CreateRotation(radians, RectangleF.Center(path.Bounds))); + => path.Transform(new Matrix4x4(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds)))); /// /// Creates a path rotated by the specified degrees around its center. @@ -35,7 +35,7 @@ public static IPathCollection RotateDegree(this IPathCollection shape, float deg /// The translation position. /// A with a translate transform applied. public static IPathCollection Translate(this IPathCollection path, PointF position) - => path.Transform(Matrix3x2.CreateTranslation(position)); + => path.Transform(Matrix4x4.CreateTranslation(position.X, position.Y, 0)); /// /// Creates a path translated by the supplied position @@ -55,7 +55,7 @@ public static IPathCollection Translate(this IPathCollection path, float x, floa /// The amount to scale along the Y axis. /// A with a translate transform applied. public static IPathCollection Scale(this IPathCollection path, float scaleX, float scaleY) - => path.Transform(Matrix3x2.CreateScale(scaleX, scaleY, RectangleF.Center(path.Bounds))); + => path.Transform(Matrix4x4.CreateScale(scaleX, scaleY, 1, new Vector3(RectangleF.Center(path.Bounds), 0))); /// /// Creates a path translated by the supplied position @@ -64,7 +64,7 @@ public static IPathCollection Scale(this IPathCollection path, float scaleX, flo /// The amount to scale along both the x and y axis. /// A with a translate transform applied. public static IPathCollection Scale(this IPathCollection path, float scale) - => path.Transform(Matrix3x2.CreateScale(scale, RectangleF.Center(path.Bounds))); + => path.Transform(Matrix4x4.CreateScale(scale, scale, 1, new Vector3(RectangleF.Center(path.Bounds), 0))); /// /// Creates a path rotated by the specified radians around its center. @@ -73,7 +73,7 @@ public static IPathCollection Scale(this IPathCollection path, float scale) /// The radians to rotate the path. /// A with a rotate transform applied. public static IPath Rotate(this IPath path, float radians) - => path.Transform(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds))); + => path.Transform(new Matrix4x4(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds)))); /// /// Creates a path rotated by the specified degrees around its center. @@ -91,7 +91,7 @@ public static IPath RotateDegree(this IPath shape, float degree) /// The translation position. /// A with a translate transform applied. public static IPath Translate(this IPath path, PointF position) - => path.Transform(Matrix3x2.CreateTranslation(position)); + => path.Transform(Matrix4x4.CreateTranslation(position.X, position.Y, 0)); /// /// Creates a path translated by the supplied position @@ -111,7 +111,7 @@ public static IPath Translate(this IPath path, float x, float y) /// The amount to scale along the Y axis. /// A with a translate transform applied. public static IPath Scale(this IPath path, float scaleX, float scaleY) - => path.Transform(Matrix3x2.CreateScale(scaleX, scaleY, RectangleF.Center(path.Bounds))); + => path.Transform(Matrix4x4.CreateScale(scaleX, scaleY, 1, new Vector3(RectangleF.Center(path.Bounds), 0))); /// /// Creates a path translated by the supplied position @@ -120,7 +120,7 @@ public static IPath Scale(this IPath path, float scaleX, float scaleY) /// The amount to scale along both the x and y axis. /// A with a translate transform applied. public static IPath Scale(this IPath path, float scale) - => path.Transform(Matrix3x2.CreateScale(scale, RectangleF.Center(path.Bounds))); + => path.Transform(Matrix4x4.CreateScale(scale, scale, 1, new Vector3(RectangleF.Center(path.Bounds), 0))); /// /// Calculates the approximate length of the path as though each segment were unrolled into a line. diff --git a/src/ImageSharp.Drawing/Shapes/PathTypes.cs b/src/ImageSharp.Drawing/PathTypes.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/PathTypes.cs rename to src/ImageSharp.Drawing/PathTypes.cs diff --git a/src/ImageSharp.Drawing/Shapes/PointOrientation.cs b/src/ImageSharp.Drawing/PointOrientation.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/PointOrientation.cs rename to src/ImageSharp.Drawing/PointOrientation.cs diff --git a/src/ImageSharp.Drawing/Shapes/Polygon.cs b/src/ImageSharp.Drawing/Polygon.cs similarity index 98% rename from src/ImageSharp.Drawing/Shapes/Polygon.cs rename to src/ImageSharp.Drawing/Polygon.cs index e928d32e6..469a836ae 100644 --- a/src/ImageSharp.Drawing/Shapes/Polygon.cs +++ b/src/ImageSharp.Drawing/Polygon.cs @@ -78,7 +78,7 @@ internal Polygon(ILineSegment[] segments, bool owned) public override bool IsClosed => true; /// - public override IPath Transform(Matrix3x2 matrix) + public override IPath Transform(Matrix4x4 matrix) { if (matrix.IsIdentity) { diff --git a/src/ImageSharp.Drawing/Shapes/PolygonGeometry/ClippedShapeGenerator.cs b/src/ImageSharp.Drawing/PolygonGeometry/ClippedShapeGenerator.cs similarity index 98% rename from src/ImageSharp.Drawing/Shapes/PolygonGeometry/ClippedShapeGenerator.cs rename to src/ImageSharp.Drawing/PolygonGeometry/ClippedShapeGenerator.cs index d423b57aa..6f2e36f75 100644 --- a/src/ImageSharp.Drawing/Shapes/PolygonGeometry/ClippedShapeGenerator.cs +++ b/src/ImageSharp.Drawing/PolygonGeometry/ClippedShapeGenerator.cs @@ -5,7 +5,7 @@ using PCPolygon = SixLabors.PolygonClipper.Polygon; using PolygonClipperAction = SixLabors.PolygonClipper.PolygonClipper; -namespace SixLabors.ImageSharp.Drawing.Shapes.PolygonGeometry; +namespace SixLabors.ImageSharp.Drawing.PolygonGeometry; /// /// Generates clipped shapes from one or more input paths using polygon boolean operations. diff --git a/src/ImageSharp.Drawing/Shapes/PolygonGeometry/PolygonClipperFactory.cs b/src/ImageSharp.Drawing/PolygonGeometry/PolygonClipperFactory.cs similarity index 97% rename from src/ImageSharp.Drawing/Shapes/PolygonGeometry/PolygonClipperFactory.cs rename to src/ImageSharp.Drawing/PolygonGeometry/PolygonClipperFactory.cs index dfe11f4d4..454820488 100644 --- a/src/ImageSharp.Drawing/Shapes/PolygonGeometry/PolygonClipperFactory.cs +++ b/src/ImageSharp.Drawing/PolygonGeometry/PolygonClipperFactory.cs @@ -4,7 +4,7 @@ using SixLabors.PolygonClipper; using PCPolygon = SixLabors.PolygonClipper.Polygon; -namespace SixLabors.ImageSharp.Drawing.Shapes.PolygonGeometry; +namespace SixLabors.ImageSharp.Drawing.PolygonGeometry; /// /// Builders for from ImageSharp paths. diff --git a/src/ImageSharp.Drawing/PolygonGeometry/StrokedShapeGenerator.cs b/src/ImageSharp.Drawing/PolygonGeometry/StrokedShapeGenerator.cs new file mode 100644 index 000000000..d93f0fd6d --- /dev/null +++ b/src/ImageSharp.Drawing/PolygonGeometry/StrokedShapeGenerator.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.PolygonClipper; + +using PCPolygon = SixLabors.PolygonClipper.Polygon; +using StrokeOptions = SixLabors.ImageSharp.Drawing.Processing.StrokeOptions; + +namespace SixLabors.ImageSharp.Drawing.PolygonGeometry; + +/// +/// Generates stroked and merged shapes using polygon stroking and boolean clipping. +/// +internal static class StrokedShapeGenerator +{ + /// + /// Strokes a path and returns a merged outline from its flattened segments. + /// + /// The source path. It is flattened using the current flattening settings. + /// The stroke width in the caller's coordinate space. + /// The stroke geometry options. + /// + /// A representing the stroked outline after boolean merge. + /// + public static ComplexPolygon GenerateStrokedShapes(IPath path, float width, StrokeOptions options) + { + // 1) Stroke the input path as open or closed. + PCPolygon rings = []; + + foreach (ISimplePath sp in path.Flatten()) + { + ReadOnlySpan span = sp.Points.Span; + + if (span.Length < 2) + { + continue; + } + + Contour ring = new(span.Length); + for (int i = 0; i < span.Length; i++) + { + PointF p = span[i]; + ring.Add(new Vertex(p.X, p.Y)); + } + + if (sp.IsClosed) + { + ring.Add(ring[0]); + } + + rings.Add(ring); + } + + int count = rings.Count; + if (count == 0) + { + return new([]); + } + + PCPolygon result = PolygonStroker.Stroke(rings, width, CreateStrokeOptions(options)); + + IPath[] shapes = new IPath[result.Count]; + int index = 0; + for (int i = 0; i < result.Count; i++) + { + Contour contour = result[i]; + PointF[] points = new PointF[contour.Count]; + + for (int j = 0; j < contour.Count; j++) + { + Vertex vertex = contour[j]; + points[j] = new PointF((float)vertex.X, (float)vertex.Y); + } + + shapes[index++] = new Polygon(points); + } + + return new(shapes); + } + + private static PolygonClipper.StrokeOptions CreateStrokeOptions(StrokeOptions options) + { + PolygonClipper.StrokeOptions o = new() + { + ArcDetailScale = options.ArcDetailScale, + MiterLimit = options.MiterLimit, + InnerMiterLimit = options.InnerMiterLimit, + LineJoin = options.LineJoin switch + { + LineJoin.MiterRound => PolygonClipper.LineJoin.MiterRound, + LineJoin.Bevel => PolygonClipper.LineJoin.Bevel, + LineJoin.Round => PolygonClipper.LineJoin.Round, + LineJoin.MiterRevert => PolygonClipper.LineJoin.MiterRevert, + _ => PolygonClipper.LineJoin.Miter, + }, + + InnerJoin = options.InnerJoin switch + { + InnerJoin.Round => PolygonClipper.InnerJoin.Round, + InnerJoin.Miter => PolygonClipper.InnerJoin.Miter, + InnerJoin.Jag => PolygonClipper.InnerJoin.Jag, + _ => PolygonClipper.InnerJoin.Bevel, + }, + + LineCap = options.LineCap switch + { + LineCap.Round => PolygonClipper.LineCap.Round, + LineCap.Square => PolygonClipper.LineCap.Square, + _ => PolygonClipper.LineCap.Butt, + }, + NormalizeOutput = options.NormalizeOutput + }; + + return o; + } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/CanvasRegionFrame{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Backends/CanvasRegionFrame{TPixel}.cs new file mode 100644 index 000000000..2714017f2 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/CanvasRegionFrame{TPixel}.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Frame adapter that exposes a clipped subregion of another frame. +/// +/// The pixel format. +internal sealed class CanvasRegionFrame : ICanvasFrame + where TPixel : unmanaged, IPixel +{ + private readonly ICanvasFrame parent; + private readonly Rectangle region; + + public CanvasRegionFrame(ICanvasFrame parent, Rectangle region) + { + Guard.NotNull(parent, nameof(parent)); + Guard.MustBeGreaterThanOrEqualTo(region.Width, 0, nameof(region)); + Guard.MustBeGreaterThanOrEqualTo(region.Height, 0, nameof(region)); + this.parent = parent; + this.region = region; + } + + public Rectangle Bounds => new( + this.parent.Bounds.X + this.region.X, + this.parent.Bounds.Y + this.region.Y, + this.region.Width, + this.region.Height); + + public bool TryGetCpuRegion(out Buffer2DRegion region) + { + if (!this.parent.TryGetCpuRegion(out Buffer2DRegion parentRegion)) + { + region = default; + return false; + } + + region = parentRegion.GetSubRegion(this.region); + return true; + } + + public bool TryGetNativeSurface([NotNullWhen(true)] out NativeSurface? surface) + => this.parent.TryGetNativeSurface(out surface); +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/CompositionBatch.cs b/src/ImageSharp.Drawing/Processing/Backends/CompositionBatch.cs new file mode 100644 index 000000000..1dc119c31 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/CompositionBatch.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Prepared composition data emitted by and consumed by backends. +/// +public sealed class CompositionBatch +{ + /// + /// Initializes a new instance of the class. + /// + /// The coverage definition for this batch. + /// Prepared composition commands in draw order. + /// The flush identifier shared by all batches in one flush call. + /// Whether this is the last batch for the current flush. + /// Optional destination-local bounds touched by this batch. + public CompositionBatch( + in CompositionCoverageDefinition definition, + List commands, + int flushId = 0, + bool isFinalBatchInFlush = true, + Rectangle? compositionBounds = null) + { + this.Definition = definition; + this.Commands = commands; + this.FlushId = flushId; + this.IsFinalBatchInFlush = isFinalBatchInFlush; + this.CompositionBounds = compositionBounds; + } + + /// + /// Gets the coverage definition that should be rasterized once per flush. + /// + public CompositionCoverageDefinition Definition { get; } + + /// + /// Gets normalized composition commands in original draw order. + /// + public List Commands { get; } + + /// + /// Gets the batcher flush identifier shared by all batches emitted from one canvas flush call. + /// + public int FlushId { get; } + + /// + /// Gets a value indicating whether this is the last batch emitted for the current flush identifier. + /// + public bool IsFinalBatchInFlush { get; } + + /// + /// Gets the destination-local bounds touched by this batch or scene flush when known. + /// + /// + /// GPU backends can use this region to limit destination initialization, composition, and readback + /// to modified pixels. + /// + public Rectangle? CompositionBounds { get; } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/CompositionCommand.cs b/src/ImageSharp.Drawing/Processing/Backends/CompositionCommand.cs new file mode 100644 index 000000000..f952bd69a --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/CompositionCommand.cs @@ -0,0 +1,217 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// One normalized composition command queued by . +/// +public readonly struct CompositionCommand +{ + /// + /// Initializes a new instance of the struct. + /// + /// Stable definition key used for composition-level caching. + /// Path to rasterize in target-local coordinates. + /// Brush used during composition. + /// Brush bounds used for applicator creation. + /// Graphics options used for composition. + /// Rasterizer options used to generate coverage. + /// Absolute destination offset where coverage is composited. + /// Optional stroke options for backend-side stroke expansion. + /// Stroke width in pixels when is present. + /// Optional dash pattern when is present. + private CompositionCommand( + int definitionKey, + IPath path, + Brush brush, + Rectangle brushBounds, + GraphicsOptions graphicsOptions, + in RasterizerOptions rasterizerOptions, + Point destinationOffset, + StrokeOptions? strokeOptions, + float strokeWidth, + ReadOnlyMemory strokePattern) + { + this.DefinitionKey = definitionKey; + this.Path = path; + this.Brush = brush; + this.BrushBounds = brushBounds; + this.GraphicsOptions = graphicsOptions; + this.RasterizerOptions = rasterizerOptions; + this.DestinationOffset = destinationOffset; + this.StrokeOptions = strokeOptions; + this.StrokeWidth = strokeWidth; + this.StrokePattern = strokePattern; + } + + /// + /// Gets a stable definition key used for composition-level caching. + /// + public int DefinitionKey { get; } + + /// + /// Gets the flattened path to rasterize in target-local coordinates. + /// All sub-paths are pre-flattened and oriented for correct fill rasterization. + /// + public IPath Path { get; } + + /// + /// Gets the brush used during composition. + /// + public Brush Brush { get; } + + /// + /// Gets brush bounds used for applicator creation. + /// + public Rectangle BrushBounds { get; } + + /// + /// Gets graphics options used for composition. + /// + public GraphicsOptions GraphicsOptions { get; } + + /// + /// Gets rasterizer options used to generate coverage. + /// + public RasterizerOptions RasterizerOptions { get; } + + /// + /// Gets the absolute destination offset where the local coverage should be composited. + /// + public Point DestinationOffset { get; } + + /// + /// Gets the stroke options when this command represents a stroke operation. + /// + public StrokeOptions? StrokeOptions { get; } + + /// + /// Gets the stroke width in pixels. + /// + public float StrokeWidth { get; } + + /// + /// Gets the optional dash pattern. + /// + public ReadOnlyMemory StrokePattern { get; } + + /// + /// Creates a fill composition command. + /// + /// Path to rasterize in target-local coordinates. + /// Brush used during composition. + /// Graphics options used for composition. + /// Rasterizer options used to generate coverage. + /// Absolute destination offset where coverage is composited. + /// Optional scoped cache to avoid repeated path flattening for the same reference. + /// The normalized composition command. + public static CompositionCommand Create( + IPath path, + Brush brush, + GraphicsOptions graphicsOptions, + in RasterizerOptions rasterizerOptions, + Point destinationOffset = default, + Dictionary? definitionKeyCache = null) + { + int definitionKey = ComputeCoverageDefinitionKey(path, in rasterizerOptions, definitionKeyCache); + Rectangle brushBounds = ComputeBrushBounds(path, destinationOffset); + + return new( + definitionKey, + path, + brush, + brushBounds, + graphicsOptions, + in rasterizerOptions, + destinationOffset, + null, + 0f, + default); + } + + /// + /// Creates a stroke composition command where the backend is responsible for stroke expansion. + /// + /// The original centerline path in target-local coordinates. + /// Brush used during composition. + /// Graphics options used for composition. + /// Rasterizer options with interest inflated for stroke bounds. + /// Stroke geometry options. + /// Stroke width in pixels. + /// Optional dash pattern. Each element is a multiple of . + /// Absolute destination offset where coverage is composited. + /// Optional scoped cache to avoid repeated path flattening for the same reference. + /// The normalized stroke composition command. + public static CompositionCommand CreateStroke( + IPath path, + Brush brush, + GraphicsOptions graphicsOptions, + in RasterizerOptions rasterizerOptions, + StrokeOptions strokeOptions, + float strokeWidth, + ReadOnlyMemory strokePattern = default, + Point destinationOffset = default, + Dictionary? definitionKeyCache = null) + { + int definitionKey = ComputeCoverageDefinitionKey(path, in rasterizerOptions, definitionKeyCache); + Rectangle brushBounds = ComputeBrushBounds(rasterizerOptions.Interest, destinationOffset); + + return new( + definitionKey, + path, + brush, + brushBounds, + graphicsOptions, + in rasterizerOptions, + destinationOffset, + strokeOptions, + strokeWidth, + strokePattern); + } + + /// + /// Computes a coverage definition key from path identity and rasterization state. + /// + /// Path to rasterize. + /// Rasterizer options used for coverage generation. + /// Unused. Retained for API compatibility. + /// A stable key for coverage-equivalent commands. + public static int ComputeCoverageDefinitionKey( + IPath path, + in RasterizerOptions rasterizerOptions, + Dictionary? definitionKeyCache = null) + { + int pathIdentity = RuntimeHelpers.GetHashCode(path); + int rasterState = HashCode.Combine( + rasterizerOptions.Interest.Size, + (int)rasterizerOptions.IntersectionRule, + (int)rasterizerOptions.RasterizationMode, + (int)rasterizerOptions.SamplingOrigin); + return HashCode.Combine(pathIdentity, rasterState); + } + + private static Rectangle ComputeBrushBounds(IPath path, Point destinationOffset) + { + RectangleF bounds = path.Bounds; + Rectangle localBrushBounds = Rectangle.FromLTRB( + (int)MathF.Floor(bounds.Left), + (int)MathF.Floor(bounds.Top), + (int)MathF.Ceiling(bounds.Right), + (int)MathF.Ceiling(bounds.Bottom)); + return new( + localBrushBounds.X + destinationOffset.X, + localBrushBounds.Y + destinationOffset.Y, + localBrushBounds.Width, + localBrushBounds.Height); + } + + private static Rectangle ComputeBrushBounds(Rectangle interest, Point destinationOffset) + => new( + interest.X + destinationOffset.X, + interest.Y + destinationOffset.Y, + interest.Width, + interest.Height); +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/CompositionCoverageDefinition.cs b/src/ImageSharp.Drawing/Processing/Backends/CompositionCoverageDefinition.cs new file mode 100644 index 000000000..f3c93f390 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/CompositionCoverageDefinition.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// One coverage definition that can be rasterized once and reused by multiple composition commands. +/// +public readonly struct CompositionCoverageDefinition +{ + /// + /// Initializes a new instance of the struct. + /// + /// The stable key for this coverage definition. + /// The path used to generate coverage. + /// The rasterizer options used to generate coverage. + /// The absolute destination offset where coverage is composited. + public CompositionCoverageDefinition( + int definitionKey, + IPath path, + in RasterizerOptions rasterizerOptions, + Point destinationOffset = default) + : this(definitionKey, path, in rasterizerOptions, destinationOffset, null, 0f, default) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The stable key for this coverage definition. + /// The path used to generate coverage. + /// The rasterizer options used to generate coverage. + /// The absolute destination offset where coverage is composited. + /// Optional stroke options. When present the path is the original centerline and the backend is responsible for stroke expansion. + /// The stroke width in pixels. Only meaningful when is not . + /// Optional dash pattern. Each element is a multiple of . + public CompositionCoverageDefinition( + int definitionKey, + IPath path, + in RasterizerOptions rasterizerOptions, + Point destinationOffset, + StrokeOptions? strokeOptions, + float strokeWidth, + ReadOnlyMemory strokePattern) + { + this.DefinitionKey = definitionKey; + this.Path = path; + this.RasterizerOptions = rasterizerOptions; + this.DestinationOffset = destinationOffset; + this.StrokeOptions = strokeOptions; + this.StrokeWidth = strokeWidth; + this.StrokePattern = strokePattern; + } + + /// + /// Gets the stable key for this coverage definition. + /// + public int DefinitionKey { get; } + + /// + /// Gets the closed, flattened path used to generate coverage. + /// All sub-paths are pre-flattened and oriented for correct fill rasterization. + /// + public IPath Path { get; } + + /// + /// Gets the rasterizer options used to generate coverage. + /// + public RasterizerOptions RasterizerOptions { get; } + + /// + /// Gets the absolute destination offset where coverage is composited. + /// + public Point DestinationOffset { get; } + + /// + /// Gets the stroke options when this definition represents a stroke operation. + /// + /// + /// When not , is the original centerline and the backend + /// is responsible for stroke expansion or SDF evaluation. + /// + public StrokeOptions? StrokeOptions { get; } + + /// + /// Gets the stroke width in pixels. + /// + public float StrokeWidth { get; } + + /// + /// Gets the optional dash pattern. Each element is a multiple of . + /// + public ReadOnlyMemory StrokePattern { get; } + + /// + /// Gets a value indicating whether this definition represents a stroke operation. + /// + public bool IsStroke => this.StrokeOptions is not null; +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/CompositionScene.cs b/src/ImageSharp.Drawing/Processing/Backends/CompositionScene.cs new file mode 100644 index 000000000..12d5bfd31 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/CompositionScene.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// One flush-time scene packet containing normalized composition commands in draw order. +/// +public sealed class CompositionScene +{ + /// + /// Initializes a new instance of the class. + /// + /// The composition commands in submission order. + public CompositionScene(IReadOnlyList commands) + => this.Commands = commands; + + /// + /// Gets normalized composition commands in submission order. + /// + public IReadOnlyList Commands { get; } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/CompositionScenePlanner.cs b/src/ImageSharp.Drawing/Processing/Backends/CompositionScenePlanner.cs new file mode 100644 index 000000000..35fcffa31 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/CompositionScenePlanner.cs @@ -0,0 +1,220 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Converts scene command streams into backend-ready prepared batches. +/// +public static class CompositionScenePlanner +{ + /// + /// Creates contiguous prepared batches grouped by coverage definition key. + /// + /// Scene commands in submission order. + /// Target frame bounds in absolute coordinates. + /// Prepared contiguous batches ready for backend execution. + public static List CreatePreparedBatches( + IReadOnlyList commands, + in Rectangle targetBounds) + { + int commandCount = commands.Count; + List batches = new(EstimateBatchCapacity(commandCount)); + int index = 0; + while (index < commandCount) + { + CompositionCommand definitionCommand = commands[index]; + int definitionKey = definitionCommand.DefinitionKey; + int remainingCount = commandCount - index; + List preparedCommands = new(EstimatePreparedCommandCapacity(remainingCount)); + for (; index < commandCount; index++) + { + CompositionCommand command = commands[index]; + if (command.DefinitionKey != definitionKey) + { + break; + } + + if (TryPrepareCommand(in command, in targetBounds, out PreparedCompositionCommand prepared)) + { + preparedCommands.Add(prepared); + } + } + + if (preparedCommands.Count == 0) + { + continue; + } + + CompositionCoverageDefinition definition = + new( + definitionKey, + definitionCommand.Path, + definitionCommand.RasterizerOptions, + definitionCommand.DestinationOffset, + definitionCommand.StrokeOptions, + definitionCommand.StrokeWidth, + definitionCommand.StrokePattern); + + batches.Add(new CompositionBatch(definition, preparedCommands)); + } + + return batches; + } + + /// + /// Estimates initial capacity for the outer batch list from total scene command count. + /// + /// Total number of scene commands. + /// Suggested initial capacity for the batch list. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int EstimateBatchCapacity(int commandCount) + { + // Typical scenes reuse coverage definitions, so batch count is usually + // meaningfully lower than command count. + if (commandCount <= 8) + { + return commandCount; + } + + if (commandCount <= 128) + { + return commandCount / 2; + } + + return commandCount / 4; + } + + /// + /// Estimates initial capacity for one contiguous prepared-command run. + /// + /// Commands remaining from the current scan index. + /// Suggested initial capacity for the current prepared-command list. + /// + /// This estimate is intentionally capped for large tails because the list is + /// allocated per run during scanning rather than once per scene. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int EstimatePreparedCommandCapacity(int remainingCount) + { + // Most adjacent commands share a definition in small-medium scenes. + if (remainingCount <= 16) + { + return remainingCount; + } + + if (remainingCount <= 128) + { + return remainingCount / 2; + } + + return 64; + } + + /// + /// Re-prepares batch commands after stroke expansion so destination regions + /// and source offsets match the actual outline interest. + /// + /// The prepared commands to update in place. + /// Target frame bounds in absolute coordinates. + /// The actual interest rect computed from the expanded outline. + public static void ReprepareBatchCommands( + List commands, + Rectangle targetBounds, + Rectangle interest) + { + Span span = CollectionsMarshal.AsSpan(commands); + int writeIndex = 0; + for (int i = 0; i < span.Length; i++) + { + ref PreparedCompositionCommand cmd = ref span[i]; + + Rectangle commandDestination = new( + cmd.DestinationOffset.X + interest.X, + cmd.DestinationOffset.Y + interest.Y, + interest.Width, + interest.Height); + + Rectangle clippedDestination = Rectangle.Intersect(targetBounds, commandDestination); + if (clippedDestination.Width <= 0 || clippedDestination.Height <= 0) + { + continue; + } + + Rectangle destinationLocalRegion = new( + clippedDestination.X - targetBounds.X, + clippedDestination.Y - targetBounds.Y, + clippedDestination.Width, + clippedDestination.Height); + + Point sourceOffset = new( + clippedDestination.X - commandDestination.X, + clippedDestination.Y - commandDestination.Y); + + cmd.DestinationRegion = destinationLocalRegion; + cmd.SourceOffset = sourceOffset; + + if (writeIndex != i) + { + span[writeIndex] = span[i]; + } + + writeIndex++; + } + + if (writeIndex < commands.Count) + { + commands.RemoveRange(writeIndex, commands.Count - writeIndex); + } + } + + /// + /// Clips one scene command to target bounds and computes coverage source offset mapping. + /// + /// The source command. + /// Target frame bounds in absolute coordinates. + /// Prepared command when clipping produces visible output. + /// when the command has visible output in target bounds. + public static bool TryPrepareCommand( + in CompositionCommand command, + in Rectangle targetBounds, + out PreparedCompositionCommand prepared) + { + Rectangle interest = command.RasterizerOptions.Interest; + Rectangle commandDestination = new( + command.DestinationOffset.X + interest.X, + command.DestinationOffset.Y + interest.Y, + interest.Width, + interest.Height); + + Rectangle clippedDestination = Rectangle.Intersect(targetBounds, commandDestination); + if (clippedDestination.Width <= 0 || clippedDestination.Height <= 0) + { + prepared = default; + return false; + } + + Rectangle destinationLocalRegion = new( + clippedDestination.X - targetBounds.X, + clippedDestination.Y - targetBounds.Y, + clippedDestination.Width, + clippedDestination.Height); + + Point sourceOffset = new( + clippedDestination.X - commandDestination.X, + clippedDestination.Y - commandDestination.Y); + + prepared = new PreparedCompositionCommand( + destinationLocalRegion, + sourceOffset, + command.Brush, + command.BrushBounds, + command.GraphicsOptions, + command.DestinationOffset); + + return true; + } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/CpuDrawingBackend.cs b/src/ImageSharp.Drawing/Processing/Backends/CpuDrawingBackend.cs deleted file mode 100644 index cd2c22ed1..000000000 --- a/src/ImageSharp.Drawing/Processing/Backends/CpuDrawingBackend.cs +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Processing.Backends; - -/// -/// Default CPU drawing backend. -/// -/// -/// This backend keeps all CPU-specific scanline handling internal so higher-level processors -/// can remain backend-agnostic. -/// -internal sealed class CpuDrawingBackend : IDrawingBackend -{ - /// - /// Initializes a new instance of the class. - /// - /// Rasterizer used for CPU coverage generation. - private CpuDrawingBackend(IRasterizer primaryRasterizer) - { - Guard.NotNull(primaryRasterizer, nameof(primaryRasterizer)); - this.PrimaryRasterizer = primaryRasterizer; - } - - /// - /// Gets the default backend instance. - /// - public static CpuDrawingBackend Instance { get; } = new(DefaultRasterizer.Instance); - - /// - /// Gets the primary rasterizer used by this backend. - /// - public IRasterizer PrimaryRasterizer { get; } - - /// - /// Creates a backend that uses the given rasterizer as the primary implementation. - /// - /// Primary rasterizer. - /// A backend instance. - public static CpuDrawingBackend Create(IRasterizer rasterizer) - { - Guard.NotNull(rasterizer, nameof(rasterizer)); - return ReferenceEquals(rasterizer, DefaultRasterizer.Instance) ? Instance : new CpuDrawingBackend(rasterizer); - } - - /// - public void FillPath( - Configuration configuration, - ImageFrame source, - IPath path, - Brush brush, - in GraphicsOptions graphicsOptions, - in RasterizerOptions rasterizerOptions, - Rectangle brushBounds, - MemoryAllocator allocator) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(path, nameof(path)); - Guard.NotNull(brush, nameof(brush)); - Guard.NotNull(allocator, nameof(allocator)); - - Rectangle interest = rasterizerOptions.Interest; - if (interest.Equals(Rectangle.Empty)) - { - return; - } - - // Detect the common "opaque solid without blending" case and bypass brush sampling - // for fully covered runs. - TPixel solidBrushColor = default; - bool isSolidBrushWithoutBlending = false; - if (brush is SolidBrush solidBrush && graphicsOptions.IsOpaqueColorWithoutBlending(solidBrush.Color)) - { - isSolidBrushWithoutBlending = true; - solidBrushColor = solidBrush.Color.ToPixel(); - } - - int minX = interest.Left; - using BrushApplicator applicator = brush.CreateApplicator(configuration, graphicsOptions, source, brushBounds); - FillRasterizationState state = new( - source, - applicator, - minX, - isSolidBrushWithoutBlending, - solidBrushColor); - - this.PrimaryRasterizer.Rasterize(path, rasterizerOptions, allocator, ref state, ProcessRasterizedScanline); - } - - /// - public void RasterizeCoverage( - IPath path, - in RasterizerOptions rasterizerOptions, - MemoryAllocator allocator, - Buffer2D destination) - { - Guard.NotNull(path, nameof(path)); - Guard.NotNull(allocator, nameof(allocator)); - Guard.NotNull(destination, nameof(destination)); - - CoverageRasterizationState state = new(destination); - this.PrimaryRasterizer.Rasterize(path, rasterizerOptions, allocator, ref state, ProcessCoverageScanline); - } - - /// - /// Copies one rasterized coverage row into the destination coverage buffer. - /// - /// Destination row index. - /// Source coverage row. - /// Callback state containing destination storage. - private static void ProcessCoverageScanline(int y, Span scanline, ref CoverageRasterizationState state) - { - Span destination = state.Buffer.DangerousGetRowSpan(y); - scanline.CopyTo(destination); - } - - /// - /// Dispatches rasterized coverage to either the generic brush path or the opaque-solid fast path. - /// - /// The pixel format. - /// Destination row index. - /// Rasterized coverage row. - /// Callback state. - private static void ProcessRasterizedScanline(int y, Span scanline, ref FillRasterizationState state) - where TPixel : unmanaged, IPixel - { - if (state.IsSolidBrushWithoutBlending) - { - ApplyCoverageRunsForOpaqueSolidBrush(state.Source, state.Applicator, scanline, state.MinX, y, state.SolidBrushColor); - } - else - { - ApplyPositiveCoverageRuns(state.Applicator, scanline, state.MinX, y); - } - } - - /// - /// Applies a brush to contiguous positive-coverage runs on a scanline. - /// - /// - /// The rasterizer has already resolved the fill rule (NonZero or EvenOdd) into per-pixel - /// coverage values. This method simply consumes the resulting positive runs. - /// - /// The pixel format. - /// Brush applicator. - /// Coverage values for one row. - /// Absolute X of scanline index 0. - /// Destination row index. - private static void ApplyPositiveCoverageRuns(BrushApplicator applicator, Span scanline, int minX, int y) - where TPixel : unmanaged, IPixel - { - int i = 0; - while (i < scanline.Length) - { - while (i < scanline.Length && scanline[i] <= 0F) - { - i++; - } - - int runStart = i; - while (i < scanline.Length && scanline[i] > 0F) - { - i++; - } - - int runLength = i - runStart; - if (runLength > 0) - { - // Apply only the positive-coverage run. This avoids invoking brush logic - // for fully transparent gaps. - applicator.Apply(scanline.Slice(runStart, runLength), minX + runStart, y); - } - } - } - - /// - /// Applies coverage using a mixed strategy for opaque solid brushes. - /// - /// - /// Semi-transparent edges still go through brush blending, but fully covered interior runs - /// are written directly with . - /// - /// The pixel format. - /// Destination frame. - /// Brush applicator for non-opaque segments. - /// Coverage values for one row. - /// Absolute X of scanline index 0. - /// Destination row index. - /// Pre-converted solid color for direct writes. - private static void ApplyCoverageRunsForOpaqueSolidBrush( - ImageFrame source, - BrushApplicator applicator, - Span scanline, - int minX, - int y, - TPixel solidBrushColor) - where TPixel : unmanaged, IPixel - { - Span destinationRow = source.PixelBuffer.DangerousGetRowSpan(y).Slice(minX, scanline.Length); - int i = 0; - - while (i < scanline.Length) - { - while (i < scanline.Length && scanline[i] <= 0F) - { - i++; - } - - int runStart = i; - while (i < scanline.Length && scanline[i] > 0F) - { - i++; - } - - int runEnd = i; - if (runEnd <= runStart) - { - continue; - } - - // Leading partially-covered segment. - int opaqueStart = runStart; - while (opaqueStart < runEnd && scanline[opaqueStart] < 1F) - { - opaqueStart++; - } - - if (opaqueStart > runStart) - { - int prefixLength = opaqueStart - runStart; - applicator.Apply(scanline.Slice(runStart, prefixLength), minX + runStart, y); - } - - // Trailing partially-covered segment. - int opaqueEnd = runEnd; - while (opaqueEnd > opaqueStart && scanline[opaqueEnd - 1] < 1F) - { - opaqueEnd--; - } - - // Fully covered interior can skip blending entirely. - if (opaqueEnd > opaqueStart) - { - destinationRow[opaqueStart..opaqueEnd].Fill(solidBrushColor); - } - - if (runEnd > opaqueEnd) - { - int suffixLength = runEnd - opaqueEnd; - applicator.Apply(scanline.Slice(opaqueEnd, suffixLength), minX + opaqueEnd, y); - } - } - } - - /// - /// Callback state used while writing coverage maps. - /// - private readonly struct CoverageRasterizationState - { - /// - /// Initializes a new instance of the struct. - /// - /// Destination coverage buffer. - public CoverageRasterizationState(Buffer2D buffer) => this.Buffer = buffer; - - /// - /// Gets the destination coverage buffer. - /// - public Buffer2D Buffer { get; } - } - - /// - /// Callback state used while filling into an image frame. - /// - /// The pixel format. - private readonly struct FillRasterizationState - where TPixel : unmanaged, IPixel - { - /// - /// Initializes a new instance of the struct. - /// - /// Destination frame. - /// Brush applicator for blended segments. - /// Absolute X corresponding to scanline index 0. - /// - /// Indicates whether opaque solid fast-path writes are allowed. - /// - /// Pre-converted opaque solid color. - public FillRasterizationState( - ImageFrame source, - BrushApplicator applicator, - int minX, - bool isSolidBrushWithoutBlending, - TPixel solidBrushColor) - { - this.Source = source; - this.Applicator = applicator; - this.MinX = minX; - this.IsSolidBrushWithoutBlending = isSolidBrushWithoutBlending; - this.SolidBrushColor = solidBrushColor; - } - - /// - /// Gets the destination frame. - /// - public ImageFrame Source { get; } - - /// - /// Gets the brush applicator used for blended segments. - /// - public BrushApplicator Applicator { get; } - - /// - /// Gets the absolute X origin of the current scanline. - /// - public int MinX { get; } - - /// - /// Gets a value indicating whether opaque interior runs can be direct-filled. - /// - public bool IsSolidBrushWithoutBlending { get; } - - /// - /// Gets the pre-converted solid color used by the opaque fast path. - /// - public TPixel SolidBrushColor { get; } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Backends/DEFAULT_DRAWING_BACKEND.md b/src/ImageSharp.Drawing/Processing/Backends/DEFAULT_DRAWING_BACKEND.md new file mode 100644 index 000000000..ae7266fe3 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/DEFAULT_DRAWING_BACKEND.md @@ -0,0 +1,249 @@ +# DefaultDrawingBackend + +This document describes the CPU-based rasterization and composition pipeline implemented by `DefaultDrawingBackend`. + +## Overview + +`DefaultDrawingBackend` is a singleton (`DefaultDrawingBackend.Instance`) implementing `IDrawingBackend`. It performs all path rasterization, brush application, and pixel compositing on the CPU using fixed-point scanline math with band-based parallelism. + +## End-to-End Flow + +```text +DrawingCanvasBatcher.FlushCompositions() + -> IDrawingBackend.FlushCompositions(configuration, target, scene) + -> target.TryGetCpuRegion(out region) + -> CompositionScenePlanner.CreatePreparedBatches(commands, targetBounds) + -> clip each command to target bounds + -> group contiguous commands by DefinitionKey + -> keep prepared destination/source offsets + -> for each CompositionBatch: + -> FlushPreparedBatch(configuration, region, batch) + -> create BrushApplicator[] for all commands in batch + -> create RowOperation (scanline callback) + -> if batch.Definition.IsStroke: + -> DefaultRasterizer.RasterizeStrokeRows(definition, rowHandler, allocator) + -> else: + -> DefaultRasterizer.RasterizeRows(definition, rowHandler, allocator) + -> dispose applicators +``` + +## Scene Planning (CompositionScenePlanner) + +`CompositionScenePlanner.CreatePreparedBatches()` transforms raw `CompositionCommand` lists into `CompositionBatch` groups: + +1. **Clip** each command's destination to target bounds; discard commands with zero-area overlap. +2. **Group** contiguous commands sharing the same `DefinitionKey` (path identity + rasterizer options hash) into a single batch. Commands with different definitions break the batch. +3. **Prepare** each command with clipped `DestinationRegion` and `SourceOffset` mapping rasterized coverage to the clipped region. + +For stroke batches, after dash expansion grows the interest rect, `ReprepareBatchCommands()` re-clips all commands to the updated bounds. + +## DefaultRasterizer + +The rasterizer is a 3300-line fixed-point scanline engine in `DefaultRasterizer.cs`. + +### Fixed-Point Representation + +``` +Format: 24.8 (8 fractional bits) +One: 256 +Sub-pixels: 256 steps per pixel +Area scale: 512 (area-to-coverage shift = 9) +``` + +### Edge Table Construction + +Input paths are flattened to line segments. Each segment is converted to fixed-point `EdgeData` (x0, y0, x1, y1) with: +- Vertical Liang-Barsky clipping to the band bounds +- Horizontal edges filtered out (fy0 == fy1) +- Y-ordering enforced (swap if y0 > y1) + +### Execution Modes + +```text +IF tileCount == 1 OR edgeCount <= 64 + -> RasterizeSingleTileDirect (no parallelism overhead) +ELSE IF ProcessorCount >= 2 + -> Parallel.For across tiles (band-sorted edges) +ELSE + -> Sequential band loop with reusable scratch +``` + +**Parallel:** `MaxDegreeOfParallelism = min(12, ProcessorCount, tileCount)`. Each worker gets an isolated `WorkerScratch` instance. No shared mutable state during tile processing. + +**Sequential:** Single reusable `WorkerScratch`, band loop with context reset between bands. + +### Band-Based Processing + +Tile height: 16 pixels. Each band processes only edges that intersect its Y range. Edges are duplicated across all touched bands during band-sort. + +### Per-Band Scan Conversion (Context) + +The `Context` ref struct holds per-band working memory: + +| Buffer | Purpose | +|---|---| +| `bitVectors[]` | Sparse touched-column tracking (nuint[] per row) | +| `coverArea[]` | Signed area accumulation (2 ints per pixel: delta + area) | +| `startCover[]` | Carry cover from edges left of the band's X origin | +| `rowMinTouchedColumn[]` | Left bound per row for sparse iteration | +| `rowMaxTouchedColumn[]` | Right bound per row for sparse iteration | +| `rowHasBits[]`, `rowTouched[]` | Touch flags for sparse reset | +| `touchedRows[]` | Touched row indices for cleanup | +| `scanline[]` | Output coverage buffer (float per pixel) | + +### Edge Rasterization + +Bresenham-style line algorithm. For each cell an edge crosses: +- Register signed delta at cell entry X +- Register -delta at cell exit X +- Update area accumulation based on cell-fraction coverage +- Track touched rows and columns via bit vectors + +### Row Coverage Emission + +For each touched row: +1. Iterate only set bits in the touched-column bit vector (sparse). +2. Accumulate winding number from `startCover` + running `coverArea` deltas. +3. Apply fill rule: + - **NonZero:** Clamp |winding| to [0, 1] + - **EvenOdd:** `(winding & mask) > CoverageStepCount ? 0 : 1` +4. Apply antialiasing mode: + - **Antialiased:** Coverage = area / 512, clamped to [0, 1] + - **Aliased:** Coverage > threshold → 1.0, else 0.0 +5. Coalesce consecutive cells with equal coverage into spans. +6. Emit span via `RasterizerCoverageRowHandler` callback. + +### Memory Budget + +``` +Band memory budget: 64 MB +Bytes per row = (wordsPerRow × pointer_size) + (coverStride × 4) + 4 +Max band rows = min(height, 64MB / bytesPerRow) +``` + +### Sparse Reset + +After emitting all rows in a band, only touched rows are cleared—not the full scratch buffer. This avoids full-buffer clears when geometry is sparse relative to the interest rectangle. + +## Stroke Processing + +### Stroke Expansion + +For each stroke definition, the rasterizer performs per-band parallel expansion: + +1. **Centerline collection:** Flatten path into contours with open/closed tracking. +2. **Dash splitting (optional):** If the definition has a dash pattern, `SplitPathExtensions.GenerateDashes()` segments the centerline into open dash sub-paths. +3. **Stroke edge descriptors:** Each contour segment produces: + - **Side edges** (Flags=None): Left/right offset by halfWidth along the perpendicular + - **Join edges** (Flags=Join): Vertex + adjacent vertex for miter/round/bevel computation + - **Cap edges** (CapStart/CapEnd): Endpoint + direction for butt/square/round caps + +### Join Expansion + +| Join Type | Algorithm | +|---|---| +| Miter | Intersection of offset lines; revert to bevel if miterDist > miterLimit × halfWidth | +| MiterRound | Blend bevel→miter at limit distance | +| Round | Circular arc subdivided at ~0.5 × halfWidth step density | +| Bevel | Straight diagonal between offset endpoints | + +### Cap Expansion + +| Cap Type | Algorithm | +|---|---| +| Butt | Single perpendicular edge at endpoint | +| Square | Rectangle extending halfWidth beyond endpoint | +| Round | Semicircular arc subdivided at ~0.5 × halfWidth | + +### Band Sorting + +`TryBuildBandSortedStrokeEdges()` duplicates stroke edges into all touched bands. Expansion Y bounds include halfWidth × max(miterLimit, 1). Each band expands and rasterizes independently. + +## Brush Application + +After rasterization emits a coverage scanline, `RowOperation.InvokeCoverageRow()` applies brushes: + +```text +for each command overlapping this scanline row: + -> clamp coverage region to command DestinationRegion + -> compute destination pixel coordinates + -> BrushApplicator[i].Apply(coverage.Slice(...), destX, destY) +``` + +Each `BrushApplicator` (abstract base in `BrushApplicator.cs`): +1. Samples brush color at destination coordinates +2. Multiplies by coverage +3. Composites into destination via `PixelBlender` + +Concrete applicator types: Solid, LinearGradient, RadialGradient, EllipticGradient, SweepGradient, Pattern, Image, Recolor. + +## ComposeLayer (CPU Path) + +```text +IDrawingBackend.ComposeLayer(source, destination, offset, options) + -> extract CPU regions from source and destination frames + -> clamp compositing region to both bounds + -> allocate float[] amounts filled with BlendPercentage + -> for each row y in intersection: + -> srcRow = sourceRegion[y].Slice(startX, width) + -> dstRow = destRegion[dstY].Slice(dstX, width) + -> PixelBlender.Blend(config, dstRow, dstRow, srcRow, amounts) +``` + +The `PixelBlender` implements the full Porter-Duff alpha composition with configurable color blend mode and per-pixel blend percentage (layer opacity). + +## TryReadRegion + +```text +IDrawingBackend.TryReadRegion(target, sourceRect, out image) + -> target.TryGetCpuRegion(out region) + -> create Image from region sub-rectangle + -> copy pixel rows +``` + +Used by `DrawingCanvas.Process()` to read back pixels for image processing operations. + +## Composition Pipeline + +The full pixel composition formula (generalized Porter-Duff): + +``` +Cₒᵤₜ = αₛ × BlendMode(Cₛ, Cₐ) + αₐ × Cₐ × (1 - αₛ) +αₒᵤₜ = αₛ + αₐ × (1 - αₛ) +``` + +Where: +- αₛ = source alpha (brush alpha × coverage × blend percentage) +- αₐ = destination alpha +- Cₛ = source color (from brush) +- Cₐ = destination color (backdrop) +- BlendMode = color blend operation (Normal, Multiply, Screen, Overlay, etc.) + +## Threading Model + +| Condition | Path | +|---|---| +| 1 tile OR ≤64 edges | Single-tile direct (no overhead) | +| ProcessorCount ≥ 2, multiple tiles | Parallel.For, MaxDOP = min(12, cores, tiles) | +| Single core | Sequential band loop | + +Worker isolation: Each parallel worker owns its own `WorkerScratch`. No synchronization during tile processing. Coverage emission callbacks are inherently thread-safe because each tile covers disjoint pixel rows. + +## Key Data Structures + +| Type | Purpose | +|---|---| +| `CompositionCommand` | Normalized draw instruction (path + brush + options) | +| `CompositionBatch` | Commands grouped by shared coverage definition | +| `PreparedCompositionCommand` | Command clipped to target bounds with source offset | +| `CompositionCoverageDefinition` | Stable identity for coverage reuse (path + rasterizer state) | +| `RasterizerOptions` | Interest rect, fill rule, rasterization mode, sampling origin, antialias threshold | +| `WorkerScratch` | Per-worker rasterization memory (bit vectors, area buffers, scanline) | + +## Memory Management + +- `WorkerScratch` allocated via `MemoryAllocator` with `IMemoryOwner` for pool return. +- Sequential path reuses a single `WorkerScratch` across batches if dimensions are compatible. +- Parallel path creates fresh worker-local scratch per `Parallel.For` iteration (discarded after). +- `BrushApplicator` instances are disposed after each batch flush. +- 64 MB band memory budget caps per-band allocation. diff --git a/src/ImageSharp.Drawing/Processing/Backends/DefaultDrawingBackend.cs b/src/ImageSharp.Drawing/Processing/Backends/DefaultDrawingBackend.cs new file mode 100644 index 000000000..849a4a457 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/DefaultDrawingBackend.cs @@ -0,0 +1,390 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// CPU backend that executes path coverage rasterization and brush composition directly against a CPU region. +/// +/// +/// +/// This backend provides the reference CPU implementation for composition behavior. +/// +/// +/// Flush execution is intentionally split: +/// +/// +/// +/// +/// +/// converts scene commands into prepared batches with . +/// +/// +/// +/// +/// +/// rasterizes shared coverage scanlines per batch and applies brushes in original command order. +/// +/// +/// +/// +public sealed class DefaultDrawingBackend : IDrawingBackend +{ + /// + /// Gets the default backend instance. + /// + public static DefaultDrawingBackend Instance { get; } = new(); + + /// + public void FlushCompositions( + Configuration configuration, + ICanvasFrame target, + CompositionScene compositionScene) + where TPixel : unmanaged, IPixel + { + if (compositionScene.Commands.Count == 0) + { + return; + } + + List preparedBatches = CompositionScenePlanner.CreatePreparedBatches( + compositionScene.Commands, + target.Bounds); + + // A single reusable scratch is maintained across the batch loop so sequential-path + // commands (single-tile or multi-band) avoid repeated pool allocation round-trips. + // The parallel multi-tile path creates its own per-worker scratch and ignores this one. + DefaultRasterizer.WorkerScratch? reusableScratch = null; + try + { + for (int i = 0; i < preparedBatches.Count; i++) + { + this.FlushPreparedBatch(configuration, target, preparedBatches[i], ref reusableScratch); + } + } + finally + { + reusableScratch?.Dispose(); + } + } + + /// + public void ComposeLayer( + Configuration configuration, + ICanvasFrame source, + ICanvasFrame destination, + Point destinationOffset, + GraphicsOptions options) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + + if (!source.TryGetCpuRegion(out Buffer2DRegion sourceRegion)) + { + throw new NotSupportedException($"{nameof(DefaultDrawingBackend)} requires CPU-accessible source frames."); + } + + if (!destination.TryGetCpuRegion(out Buffer2DRegion destinationRegion)) + { + throw new NotSupportedException($"{nameof(DefaultDrawingBackend)} requires CPU-accessible destination frames."); + } + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(options); + float blendPercentage = options.BlendPercentage; + + int srcWidth = sourceRegion.Width; + int srcHeight = sourceRegion.Height; + int dstWidth = destinationRegion.Width; + int dstHeight = destinationRegion.Height; + + // Clamp the compositing region to both source and destination bounds. + int startX = Math.Max(0, -destinationOffset.X); + int startY = Math.Max(0, -destinationOffset.Y); + int endX = Math.Min(srcWidth, dstWidth - destinationOffset.X); + int endY = Math.Min(srcHeight, dstHeight - destinationOffset.Y); + + if (endX <= startX || endY <= startY) + { + return; + } + + int width = endX - startX; + + // Allocate a reusable per-row amount buffer from the memory pool. + using IMemoryOwner amountsOwner = configuration.MemoryAllocator.Allocate(width); + Span amounts = amountsOwner.Memory.Span; + amounts.Fill(blendPercentage); + + for (int y = startY; y < endY; y++) + { + Span srcRow = sourceRegion.DangerousGetRowSpan(y).Slice(startX, width); + int dstX = destinationOffset.X + startX; + int dstY = destinationOffset.Y + y; + Span dstRow = destinationRegion.DangerousGetRowSpan(dstY).Slice(dstX, width); + + blender.Blend(configuration, dstRow, dstRow, srcRow, amounts); + } + } + + /// + public ICanvasFrame CreateLayerFrame( + Configuration configuration, + ICanvasFrame parentTarget, + int width, + int height) + where TPixel : unmanaged, IPixel + { + Buffer2D buffer = configuration.MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); + return new MemoryCanvasFrame(new Buffer2DRegion(buffer)); + } + + /// + public void ReleaseFrameResources( + Configuration configuration, + ICanvasFrame target) + where TPixel : unmanaged, IPixel + { + if (target.TryGetCpuRegion(out Buffer2DRegion region)) + { + region.Buffer.Dispose(); + } + } + + /// + public bool TryReadRegion( + Configuration configuration, + ICanvasFrame target, + Rectangle sourceRectangle, + Buffer2D destination) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(destination, nameof(destination)); + + // CPU backend readback is available only when the target exposes CPU pixels. + if (!target.TryGetCpuRegion(out Buffer2DRegion sourceRegion)) + { + return false; + } + + // Clamp the request to the target region to avoid out-of-range row slicing. + Rectangle clipped = Rectangle.Intersect( + new Rectangle(0, 0, sourceRegion.Width, sourceRegion.Height), + sourceRectangle); + + if (clipped.Width <= 0 || clipped.Height <= 0) + { + return false; + } + + int copyWidth = Math.Min(clipped.Width, destination.Width); + int copyHeight = Math.Min(clipped.Height, destination.Height); + for (int y = 0; y < copyHeight; y++) + { + sourceRegion.DangerousGetRowSpan(clipped.Y + y) + .Slice(clipped.X, copyWidth) + .CopyTo(destination.DangerousGetRowSpan(y)); + } + + return true; + } + + /// + /// Executes one prepared batch on the CPU. + /// + /// The destination pixel format. + /// The active processing configuration. + /// The destination frame. + /// + /// One prepared batch where all commands share the same coverage definition and differ only by brush/options. + /// + /// + /// This method is intentionally reusable so GPU backends can delegate unsupported batches + /// without reconstructing a full . + /// + internal void FlushPreparedBatch( + Configuration configuration, + ICanvasFrame target, + CompositionBatch compositionBatch) + where TPixel : unmanaged, IPixel + { + DefaultRasterizer.WorkerScratch? noScratch = null; + this.FlushPreparedBatch(configuration, target, compositionBatch, ref noScratch); + noScratch?.Dispose(); + } + + private void FlushPreparedBatch( + Configuration configuration, + ICanvasFrame target, + CompositionBatch compositionBatch, + ref DefaultRasterizer.WorkerScratch? reusableScratch) + where TPixel : unmanaged, IPixel + { + if (compositionBatch.Commands.Count == 0) + { + return; + } + + if (!target.TryGetCpuRegion(out Buffer2DRegion destinationFrame)) + { + throw new NotSupportedException($"{nameof(DefaultDrawingBackend)} requires CPU-accessible frame targets."); + } + + CompositionCoverageDefinition definition = compositionBatch.Definition; + + IPath rasterPath = definition.Path; + RasterizerOptions rasterizerOptions = definition.RasterizerOptions; + + if (definition.IsStroke && definition.StrokePattern.Length > 0) + { + // Dashed strokes: split into dash segments on the CPU, then stroke-expand + // each segment via the per-band parallel path (same as solid strokes). + rasterPath = rasterPath.GenerateDashes(definition.StrokeWidth, definition.StrokePattern.Span); + + // Recompute interest from the split path bounds with stroke expansion. + float halfWidth = definition.StrokeWidth * 0.5f; + float maxExtent = halfWidth * Math.Max((float)(definition.StrokeOptions?.MiterLimit ?? 4.0), 1.0f); + RectangleF pathBounds = rasterPath.Bounds; + pathBounds = new RectangleF( + pathBounds.X + 0.5F - maxExtent, + pathBounds.Y + 0.5F - maxExtent, + pathBounds.Width + (maxExtent * 2), + pathBounds.Height + (maxExtent * 2)); + + Rectangle interest = Rectangle.FromLTRB( + (int)MathF.Floor(pathBounds.Left), + (int)MathF.Floor(pathBounds.Top), + (int)MathF.Ceiling(pathBounds.Right), + (int)MathF.Ceiling(pathBounds.Bottom)); + + rasterizerOptions = new RasterizerOptions( + interest, + rasterizerOptions.IntersectionRule, + rasterizerOptions.RasterizationMode, + rasterizerOptions.SamplingOrigin, + rasterizerOptions.AntialiasThreshold); + + // Re-prepare commands with the dash-split interest so destination + // regions and source offsets are aligned with the rasterizer. + CompositionScenePlanner.ReprepareBatchCommands(compositionBatch.Commands, target.Bounds, interest); + } + + Rectangle destinationBounds = destinationFrame.Rectangle; + List commands = compositionBatch.Commands; + int commandCount = commands.Count; + BrushApplicator[] applicators = new BrushApplicator[commandCount]; + try + { + for (int i = 0; i < commandCount; i++) + { + PreparedCompositionCommand command = commands[i]; + Buffer2DRegion commandRegion = destinationFrame.GetSubRegion(command.DestinationRegion); + applicators[i] = command.Brush.CreateApplicator( + configuration, + command.GraphicsOptions, + commandRegion, + command.BrushBounds); + } + + // Stream composition directly from rasterizer scanlines so we do not allocate + // and then re-read an intermediate coverage map. + RowOperation operation = new( + commands, + applicators, + destinationBounds, + rasterizerOptions.Interest.Top); + + if (definition.IsStroke) + { + // All strokes (solid and dashed) use per-band parallel stroke expansion. + DefaultRasterizer.RasterizeStrokeRows( + rasterPath, + rasterizerOptions, + configuration.MemoryAllocator, + operation.InvokeCoverageRow, + ref reusableScratch, + definition.StrokeWidth, + definition.StrokeOptions!.LineJoin, + definition.StrokeOptions!.LineCap, + (float)definition.StrokeOptions!.MiterLimit); + } + else + { + DefaultRasterizer.RasterizeRows( + rasterPath, + rasterizerOptions, + configuration.MemoryAllocator, + operation.InvokeCoverageRow, + ref reusableScratch); + } + } + finally + { + foreach (BrushApplicator? applicator in applicators) + { + applicator?.Dispose(); + } + } + } + + private readonly struct RowOperation + where TPixel : unmanaged, IPixel + { + private readonly List commands; + private readonly BrushApplicator[] applicators; + private readonly Rectangle destinationBounds; + private readonly int coverageTop; + + public RowOperation( + List commands, + BrushApplicator[] applicators, + Rectangle destinationBounds, + int coverageTop) + { + this.commands = commands; + this.applicators = applicators; + this.destinationBounds = destinationBounds; + this.coverageTop = coverageTop; + } + + public void InvokeCoverageRow(int y, int startX, Span coverage) + { + int sourceY = y - this.coverageTop; + int rowStart = startX; + int rowEnd = startX + coverage.Length; + + Rectangle destinationBounds = this.destinationBounds; + BrushApplicator[] applicators = this.applicators; + for (int i = 0; i < this.commands.Count; i++) + { + PreparedCompositionCommand command = this.commands[i]; + Rectangle commandDestination = command.DestinationRegion; + + int commandY = sourceY - command.SourceOffset.Y; + if ((uint)commandY >= (uint)commandDestination.Height) + { + continue; + } + + int sourceStartX = command.SourceOffset.X; + int sourceEndX = sourceStartX + commandDestination.Width; + int overlapStart = Math.Max(rowStart, sourceStartX); + int overlapEnd = Math.Min(rowEnd, sourceEndX); + if (overlapEnd <= overlapStart) + { + continue; + } + + int localStart = overlapStart - rowStart; + int localLength = overlapEnd - overlapStart; + int destinationX = destinationBounds.X + commandDestination.X + (overlapStart - sourceStartX); + int destinationY = destinationBounds.Y + commandDestination.Y + commandY; + + applicators[i].Apply(coverage.Slice(localStart, localLength), destinationX, destinationY); + } + } + } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/DefaultRasterizer.cs b/src/ImageSharp.Drawing/Processing/Backends/DefaultRasterizer.cs new file mode 100644 index 000000000..61102e6f6 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/DefaultRasterizer.cs @@ -0,0 +1,3278 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Default fixed-point rasterizer that converts polygon edges into per-row coverage. +/// +/// +/// The scanner has two execution modes: +/// 1. Parallel tiled execution (default): build an edge table once, bucket edges by tile rows, +/// rasterize tiles in parallel with worker-local scratch, then emit covered rows directly. +/// 2. Sequential execution: reuse the same edge table and process band buckets on one thread. +/// +/// Both modes share the same coverage math and fill-rule handling, ensuring consistent +/// scan conversion regardless of scheduling strategy. +/// +internal static class DefaultRasterizer +{ + // Upper bound for temporary scanner buffers (bit vectors + cover/area + start-cover rows). + // Keeping this bounded prevents pathological full-image allocations on very large interests. + private const long BandMemoryBudgetBytes = 64L * 1024L * 1024L; + + // Tile height used by the parallel row-tiling pipeline. + private const int DefaultTileHeight = 16; + + // Cap worker fan-out for coverage emission + composition callbacks. + // Higher counts increased scheduling overhead for medium geometry workloads. + private const int MaxParallelWorkerCount = 12; + + private const int FixedShift = 8; + private const int FixedOne = 1 << FixedShift; + private static readonly int WordBitCount = nint.Size * 8; + private const int AreaToCoverageShift = 9; + private const int CoverageStepCount = 256; + private const int EvenOddMask = (CoverageStepCount * 2) - 1; + private const int EvenOddPeriod = CoverageStepCount * 2; + private const float CoverageScale = 1F / CoverageStepCount; + + /// + /// Rasterizes the path into trimmed coverage rows using the default execution policy. + /// + /// Path to rasterize. + /// Rasterization options. + /// Temporary buffer allocator. + /// Coverage row callback invoked once per emitted row. + public static void RasterizeRows( + IPath path, + in RasterizerOptions options, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler) + { + WorkerScratch? scratch = null; + try + { + RasterizeCoreRows(path, options, allocator, rowHandler, allowParallel: true, ref scratch); + } + finally + { + scratch?.Dispose(); + } + } + + /// + /// Rasterizes the path into trimmed coverage rows using the default execution policy, + /// optionally reusing caller-managed scratch buffers across multiple invocations. + /// + /// Path to rasterize. + /// Rasterization options. + /// Temporary buffer allocator. + /// Coverage row callback invoked once per emitted row. + /// + /// Optional caller-managed scratch. If compatible, the existing buffers are reused; otherwise + /// they are replaced. On return, holds the scratch used by + /// the sequential path (or remains when the parallel multi-tile path ran). + /// The caller is responsible for disposing the scratch after the last call. + /// + internal static void RasterizeRows( + IPath path, + in RasterizerOptions options, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + ref WorkerScratch? reusableScratch) + => RasterizeCoreRows(path, options, allocator, rowHandler, allowParallel: true, ref reusableScratch); + + /// + /// Rasterizes the path into trimmed coverage rows using forced sequential execution. + /// + /// Path to rasterize. + /// Rasterization options. + /// Temporary buffer allocator. + /// Coverage row callback invoked once per emitted row. + public static void RasterizeRowsSequential( + IPath path, + in RasterizerOptions options, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler) + { + WorkerScratch? scratch = null; + try + { + RasterizeCoreRows(path, options, allocator, rowHandler, allowParallel: false, ref scratch); + } + finally + { + scratch?.Dispose(); + } + } + + /// + /// Rasterizes a stroke path by expanding centerline edges into outline edges per-band in parallel. + /// + /// Centerline path to stroke. + /// Rasterization options (interest rect should already include stroke expansion). + /// Temporary buffer allocator. + /// Coverage row callback invoked once per emitted row. + /// Stroke width in pixels. + /// Outer join style. + /// Cap style for open contour endpoints. + /// Miter limit for miter-family joins. + public static void RasterizeStrokeRows( + IPath path, + in RasterizerOptions options, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + float strokeWidth, + LineJoin lineJoin, + LineCap lineCap, + float miterLimit) + { + WorkerScratch? scratch = null; + try + { + RasterizeStrokeCoreRows(path, options, allocator, rowHandler, allowParallel: true, ref scratch, strokeWidth, lineJoin, lineCap, miterLimit); + } + finally + { + scratch?.Dispose(); + } + } + + /// + /// Rasterizes a stroke path with caller-managed scratch reuse. + /// + internal static void RasterizeStrokeRows( + IPath path, + in RasterizerOptions options, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + ref WorkerScratch? reusableScratch, + float strokeWidth, + LineJoin lineJoin, + LineCap lineCap, + float miterLimit) + => RasterizeStrokeCoreRows(path, options, allocator, rowHandler, allowParallel: true, ref reusableScratch, strokeWidth, lineJoin, lineCap, miterLimit); + + /// + /// Shared entry point for trimmed-row rasterization. + /// + /// Path to rasterize. + /// Rasterization options. + /// Temporary buffer allocator. + /// Coverage row callback invoked once per emitted row. + /// + /// If , the scanner may use parallel tiled execution when profitable. + /// + /// + /// Caller-managed scratch for the sequential path. Updated in place when a new scratch is + /// created or when an existing scratch is incompatible and replaced. + /// + private static void RasterizeCoreRows( + IPath path, + in RasterizerOptions options, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + bool allowParallel, + ref WorkerScratch? reusableScratch) + { + Rectangle interest = options.Interest; + int width = interest.Width; + int height = interest.Height; + if (width <= 0 || height <= 0) + { + return; + } + + int wordsPerRow = BitVectorsForMaxBitCount(width); + int maxBandRows = 0; + long coverStride = (long)width * 2; + if (coverStride > int.MaxValue || !TryGetBandHeight(width, height, wordsPerRow, coverStride, out maxBandRows)) + { + ThrowInterestBoundsTooLarge(); + } + + int coverStrideInt = (int)coverStride; + bool samplePixelCenter = options.SamplingOrigin == RasterizerSamplingOrigin.PixelCenter; + float samplingOffsetX = samplePixelCenter ? 0.5F : 0F; + float samplingOffsetY = samplePixelCenter ? 0.5F : 0F; + + IEnumerable flattened = path.Flatten(); + IReadOnlyList contours = flattened is IReadOnlyList list + ? list + : [.. flattened]; + + int totalSegments = 0; + for (int i = 0; i < contours.Count; i++) + { + ISimplePath sp = contours[i]; + totalSegments += sp.IsClosed ? sp.Points.Length : sp.Points.Length - 1; + } + + if (totalSegments == 0) + { + return; + } + + using IMemoryOwner edgeDataOwner = allocator.Allocate(totalSegments); + int edgeCount = BuildEdgeTable( + contours, + interest.Left, + interest.Top, + height, + samplingOffsetX, + samplingOffsetY, + edgeDataOwner.Memory.Span); + if (edgeCount <= 0) + { + return; + } + + if (allowParallel && + TryRasterizeParallel( + edgeDataOwner.Memory, + edgeCount, + width, + height, + interest.Top, + wordsPerRow, + coverStrideInt, + maxBandRows, + options.IntersectionRule, + options.RasterizationMode, + options.AntialiasThreshold, + allocator, + rowHandler, + ref reusableScratch)) + { + return; + } + + RasterizeSequentialBands( + edgeDataOwner.Memory.Span[..edgeCount], + width, + height, + interest.Top, + wordsPerRow, + coverStrideInt, + maxBandRows, + options.IntersectionRule, + options.RasterizationMode, + options.AntialiasThreshold, + allocator, + rowHandler, + ref reusableScratch); + } + + /// + /// Shared entry point for stroke-aware trimmed-row rasterization. + /// Expands centerline edges into outline edges per-band and rasterizes directly. + /// + private static void RasterizeStrokeCoreRows( + IPath path, + in RasterizerOptions options, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + bool allowParallel, + ref WorkerScratch? reusableScratch, + float strokeWidth, + LineJoin lineJoin, + LineCap lineCap, + float miterLimit) + { + Rectangle interest = options.Interest; + int width = interest.Width; + int height = interest.Height; + if (width <= 0 || height <= 0) + { + return; + } + + int wordsPerRow = BitVectorsForMaxBitCount(width); + int maxBandRows = 0; + long coverStride = (long)width * 2; + if (coverStride > int.MaxValue || !TryGetBandHeight(width, height, wordsPerRow, coverStride, out maxBandRows)) + { + ThrowInterestBoundsTooLarge(); + } + + int coverStrideInt = (int)coverStride; + bool samplePixelCenter = options.SamplingOrigin == RasterizerSamplingOrigin.PixelCenter; + float samplingOffsetX = samplePixelCenter ? 0.5F : 0F; + float samplingOffsetY = samplePixelCenter ? 0.5F : 0F; + + // Flatten path and collect contour info, preserving open/closed state for cap/join generation. + List contours = []; + int totalVertexCount = 0; + foreach (ISimplePath sp in path.Flatten()) + { + if (sp.Points.Length < 2) + { + continue; + } + + contours.Add(sp); + totalVertexCount += sp.Points.Length; + } + + if (totalVertexCount == 0) + { + return; + } + + // Max stroke descriptors: closed contours emit 2*N (sides + joins), + // open contours emit 2*N-1 (sides + interior joins + 2 caps). + int maxEdgeCount = totalVertexCount * 2; + using IMemoryOwner edgeDataOwner = allocator.Allocate(maxEdgeCount); + int edgeCount = BuildStrokeEdgeTable( + contours, + interest.Left, + interest.Top, + samplingOffsetX, + samplingOffsetY, + edgeDataOwner.Memory.Span); + + if (edgeCount <= 0) + { + return; + } + + float halfWidth = strokeWidth / 2f; + float expansion = halfWidth * MathF.Max(miterLimit, 1f); + + if (allowParallel && + TryRasterizeStrokeParallel( + edgeDataOwner.Memory, + edgeCount, + width, + height, + interest.Top, + wordsPerRow, + coverStrideInt, + maxBandRows, + options.IntersectionRule, + options.RasterizationMode, + options.AntialiasThreshold, + allocator, + rowHandler, + ref reusableScratch, + halfWidth, + expansion, + lineJoin, + lineCap, + miterLimit)) + { + return; + } + + RasterizeStrokeSequentialBands( + edgeDataOwner.Memory.Span[..edgeCount], + width, + height, + interest.Top, + wordsPerRow, + coverStrideInt, + maxBandRows, + options.IntersectionRule, + options.RasterizationMode, + options.AntialiasThreshold, + allocator, + rowHandler, + ref reusableScratch, + halfWidth, + expansion, + lineJoin, + lineCap, + miterLimit); + } + + /// + /// Sequential implementation using band buckets over the prebuilt edge table. + /// + /// Prebuilt edges in scanner-local coordinates. + /// Destination width in pixels. + /// Destination height in pixels. + /// Absolute top Y of the interest rectangle. + /// Bit-vector words per row. + /// Cover-area stride in ints. + /// Maximum rows per reusable scratch band. + /// Fill rule. + /// Coverage mode (AA or aliased). + /// + /// Antialiasing threshold in [0, 1] when is AA. + /// + /// Temporary buffer allocator. + /// Coverage row callback invoked once per emitted row. + /// + /// Caller-managed scratch. Reused when compatible; replaced and updated in place otherwise. + /// The caller owns the lifetime and must dispose after the last use. + /// + private static void RasterizeSequentialBands( + ReadOnlySpan edges, + int width, + int height, + int interestTop, + int wordsPerRow, + int coverStrideInt, + int maxBandRows, + IntersectionRule intersectionRule, + RasterizationMode rasterizationMode, + float antialiasThreshold, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + ref WorkerScratch? reusableScratch) + { + int bandHeight = maxBandRows; + int bandCount = (height + bandHeight - 1) / bandHeight; + if (bandCount < 1) + { + return; + } + + if (!TryBuildBandSortedEdges( + edges, + bandCount, + bandHeight, + allocator, + out IMemoryOwner sortedEdgesOwner, + out IMemoryOwner bandOffsetsOwner)) + { + ThrowInterestBoundsTooLarge(); + } + + using (sortedEdgesOwner) + using (bandOffsetsOwner) + { + Span sortedEdges = sortedEdgesOwner.Memory.Span; + Span bandOffsets = bandOffsetsOwner.Memory.Span; + + // Reuse the caller-provided scratch when dimensions match; create a new one otherwise. + if (reusableScratch == null || !reusableScratch.CanReuse(wordsPerRow, coverStrideInt, width, bandHeight)) + { + reusableScratch?.Dispose(); + reusableScratch = WorkerScratch.Create(allocator, wordsPerRow, coverStrideInt, width, bandHeight); + } + + WorkerScratch scratch = reusableScratch; + for (int bandIndex = 0; bandIndex < bandCount; bandIndex++) + { + int bandTop = bandIndex * bandHeight; + int currentBandHeight = Math.Min(bandHeight, height - bandTop); + int start = bandOffsets[bandIndex]; + int length = bandOffsets[bandIndex + 1] - start; + if (length == 0) + { + // No edge crosses this band, so there is nothing to rasterize or clear. + continue; + } + + Context context = scratch.CreateContext(currentBandHeight, intersectionRule, rasterizationMode, antialiasThreshold); + context.RasterizeEdgeTable(sortedEdges.Slice(start, length), bandTop); + context.EmitCoverageRows(interestTop + bandTop, scratch.Scanline, rowHandler); + context.ResetTouchedRows(); + } + } + } + + /// + /// Attempts to execute the tiled parallel scanner. + /// + /// Memory block containing prebuilt edges. + /// Number of valid edges in . + /// Destination width in pixels. + /// Destination height in pixels. + /// Absolute top Y of the interest rectangle. + /// Bit-vector words per row. + /// Cover-area stride in ints. + /// Maximum rows per worker scratch context. + /// Fill rule. + /// Coverage mode (AA or aliased). + /// + /// Antialiasing threshold in [0, 1] when is AA. + /// + /// Temporary buffer allocator. + /// Coverage row callback invoked once per emitted row. + /// Caller-managed scratch. Reused when compatible; replaced and updated in place otherwise. + /// + /// when the tiled path executed successfully; + /// when the caller should run sequential fallback. + /// + private static bool TryRasterizeParallel( + Memory edgeMemory, + int edgeCount, + int width, + int height, + int interestTop, + int wordsPerRow, + int coverStride, + int maxBandRows, + IntersectionRule intersectionRule, + RasterizationMode rasterizationMode, + float antialiasThreshold, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + ref WorkerScratch? reusableScratch) + { + int tileHeight = Math.Min(DefaultTileHeight, maxBandRows); + if (tileHeight < 1) + { + return false; + } + + int tileCount = (height + tileHeight - 1) / tileHeight; + if (tileCount == 1 || edgeCount <= 64) + { + // Small-geometry fast path: for paths with few edges (e.g. a stroked line + // producing ~6-10 edges), iterating all edges against all rows is far cheaper + // than the allocation overhead of band sorting + Parallel.For scheduling. + RasterizeSingleTileDirect( + edgeMemory.Span[..edgeCount], + width, + height, + interestTop, + wordsPerRow, + coverStride, + intersectionRule, + rasterizationMode, + antialiasThreshold, + allocator, + rowHandler, + ref reusableScratch); + + return true; + } + + if (Environment.ProcessorCount < 2) + { + return false; + } + + if (!TryBuildBandSortedEdges( + edgeMemory.Span[..edgeCount], + tileCount, + tileHeight, + allocator, + out IMemoryOwner sortedEdgesOwner, + out IMemoryOwner tileOffsetsOwner)) + { + return false; + } + + using (sortedEdgesOwner) + using (tileOffsetsOwner) + { + Memory sortedEdgesMemory = sortedEdgesOwner.Memory; + Memory tileOffsetsMemory = tileOffsetsOwner.Memory; + + ParallelOptions parallelOptions = new() + { + MaxDegreeOfParallelism = Math.Min(MaxParallelWorkerCount, Math.Min(Environment.ProcessorCount, tileCount)) + }; + + _ = Parallel.For( + 0, + tileCount, + parallelOptions, + () => WorkerScratch.Create(allocator, wordsPerRow, coverStride, width, tileHeight), + (tileIndex, _, worker) => + { + Context context = default; + bool hasCoverage = false; + int tile = tileIndex; + int bandTop = tile * tileHeight; + try + { + Span tileOffsets = tileOffsetsMemory.Span; + int bandHeight = Math.Min(tileHeight, height - bandTop); + int start = tileOffsets[tile]; + int length = tileOffsets[tile + 1] - start; + if (length > 0) + { + ReadOnlySpan tileEdges = sortedEdgesMemory.Span.Slice(start, length); + context = worker.CreateContext(bandHeight, intersectionRule, rasterizationMode, antialiasThreshold); + context.RasterizeEdgeTable(tileEdges, bandTop); + hasCoverage = true; + context.EmitCoverageRows(interestTop + bandTop, worker.Scanline, rowHandler); + } + } + finally + { + if (hasCoverage) + { + context.ResetTouchedRows(); + } + } + + return worker; + }, + static worker => worker.Dispose()); + + return true; + } + } + + /// + /// Rasterizes a single tile directly into the caller callback. + /// + /// + /// This avoids parallel setup for tiny workloads while preserving + /// the same scan-conversion math as the general tiled path. + /// + /// Prebuilt edge table. + /// Destination width in pixels. + /// Destination height in pixels. + /// Absolute top Y of the interest rectangle. + /// Bit-vector words per row. + /// Cover-area stride in ints. + /// Fill rule. + /// Coverage mode (AA or aliased). + /// + /// Antialiasing threshold in [0, 1] when is AA. + /// + /// Temporary buffer allocator. + /// Coverage row callback invoked once per emitted row. + /// + /// Caller-managed scratch. Reused when compatible; replaced and updated in place otherwise. + /// The caller owns the lifetime and must dispose after the last use. + /// + private static void RasterizeSingleTileDirect( + ReadOnlySpan edges, + int width, + int height, + int interestTop, + int wordsPerRow, + int coverStride, + IntersectionRule intersectionRule, + RasterizationMode rasterizationMode, + float antialiasThreshold, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + ref WorkerScratch? reusableScratch) + { + // Reuse the caller-provided scratch when dimensions match; create a new one otherwise. + if (reusableScratch == null || !reusableScratch.CanReuse(wordsPerRow, coverStride, width, height)) + { + reusableScratch?.Dispose(); + reusableScratch = WorkerScratch.Create(allocator, wordsPerRow, coverStride, width, height); + } + + WorkerScratch scratch = reusableScratch; + Context context = scratch.CreateContext(height, intersectionRule, rasterizationMode, antialiasThreshold); + context.RasterizeEdgeTable(edges, bandTop: 0); + context.EmitCoverageRows(interestTop, scratch.Scanline, rowHandler); + context.ResetTouchedRows(); + } + + /// + /// Sequential stroke rasterization using band buckets over the prebuilt stroke edge table. + /// + private static void RasterizeStrokeSequentialBands( + ReadOnlySpan edges, + int width, + int height, + int interestTop, + int wordsPerRow, + int coverStrideInt, + int maxBandRows, + IntersectionRule intersectionRule, + RasterizationMode rasterizationMode, + float antialiasThreshold, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + ref WorkerScratch? reusableScratch, + float halfWidth, + float expansion, + LineJoin lineJoin, + LineCap lineCap, + float miterLimit) + { + int bandHeight = maxBandRows; + int bandCount = (height + bandHeight - 1) / bandHeight; + if (bandCount < 1) + { + return; + } + + if (!TryBuildBandSortedStrokeEdges( + edges, + bandCount, + bandHeight, + expansion, + allocator, + out IMemoryOwner sortedEdgesOwner, + out IMemoryOwner bandOffsetsOwner)) + { + ThrowInterestBoundsTooLarge(); + } + + using (sortedEdgesOwner) + using (bandOffsetsOwner) + { + Span sortedEdges = sortedEdgesOwner.Memory.Span; + Span bandOffsets = bandOffsetsOwner.Memory.Span; + + if (reusableScratch == null || !reusableScratch.CanReuse(wordsPerRow, coverStrideInt, width, bandHeight)) + { + reusableScratch?.Dispose(); + reusableScratch = WorkerScratch.Create(allocator, wordsPerRow, coverStrideInt, width, bandHeight); + } + + WorkerScratch scratch = reusableScratch; + for (int bandIndex = 0; bandIndex < bandCount; bandIndex++) + { + int bandTop = bandIndex * bandHeight; + int currentBandHeight = Math.Min(bandHeight, height - bandTop); + int start = bandOffsets[bandIndex]; + int length = bandOffsets[bandIndex + 1] - start; + if (length == 0) + { + continue; + } + + Context context = scratch.CreateContext(currentBandHeight, intersectionRule, rasterizationMode, antialiasThreshold); + context.RasterizeStrokeEdges(sortedEdges.Slice(start, length), bandTop, halfWidth, lineJoin, lineCap, miterLimit); + context.EmitCoverageRows(interestTop + bandTop, scratch.Scanline, rowHandler); + context.ResetTouchedRows(); + } + } + } + + /// + /// Attempts to execute the tiled parallel stroke scanner. + /// + private static bool TryRasterizeStrokeParallel( + Memory edgeMemory, + int edgeCount, + int width, + int height, + int interestTop, + int wordsPerRow, + int coverStride, + int maxBandRows, + IntersectionRule intersectionRule, + RasterizationMode rasterizationMode, + float antialiasThreshold, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + ref WorkerScratch? reusableScratch, + float halfWidth, + float expansion, + LineJoin lineJoin, + LineCap lineCap, + float miterLimit) + { + int tileHeight = Math.Min(DefaultTileHeight, maxBandRows); + if (tileHeight < 1) + { + return false; + } + + int tileCount = (height + tileHeight - 1) / tileHeight; + if (tileCount == 1 || edgeCount <= 64) + { + RasterizeStrokeSingleTileDirect( + edgeMemory.Span[..edgeCount], + width, + height, + interestTop, + wordsPerRow, + coverStride, + intersectionRule, + rasterizationMode, + antialiasThreshold, + allocator, + rowHandler, + ref reusableScratch, + halfWidth, + lineJoin, + lineCap, + miterLimit); + + return true; + } + + if (Environment.ProcessorCount < 2) + { + return false; + } + + if (!TryBuildBandSortedStrokeEdges( + edgeMemory.Span[..edgeCount], + tileCount, + tileHeight, + expansion, + allocator, + out IMemoryOwner sortedEdgesOwner, + out IMemoryOwner tileOffsetsOwner)) + { + return false; + } + + using (sortedEdgesOwner) + using (tileOffsetsOwner) + { + Memory sortedEdgesMemory = sortedEdgesOwner.Memory; + Memory tileOffsetsMemory = tileOffsetsOwner.Memory; + + ParallelOptions parallelOptions = new() + { + MaxDegreeOfParallelism = Math.Min(MaxParallelWorkerCount, Math.Min(Environment.ProcessorCount, tileCount)) + }; + + _ = Parallel.For( + 0, + tileCount, + parallelOptions, + () => WorkerScratch.Create(allocator, wordsPerRow, coverStride, width, tileHeight), + (tileIndex, _, worker) => + { + Context context = default; + bool hasCoverage = false; + int bandTop = tileIndex * tileHeight; + try + { + Span tileOffsets = tileOffsetsMemory.Span; + int bandHeight = Math.Min(tileHeight, height - bandTop); + int start = tileOffsets[tileIndex]; + int length = tileOffsets[tileIndex + 1] - start; + if (length > 0) + { + ReadOnlySpan tileEdges = sortedEdgesMemory.Span.Slice(start, length); + context = worker.CreateContext(bandHeight, intersectionRule, rasterizationMode, antialiasThreshold); + context.RasterizeStrokeEdges(tileEdges, bandTop, halfWidth, lineJoin, lineCap, miterLimit); + hasCoverage = true; + context.EmitCoverageRows(interestTop + bandTop, worker.Scanline, rowHandler); + } + } + finally + { + if (hasCoverage) + { + context.ResetTouchedRows(); + } + } + + return worker; + }, + static worker => worker.Dispose()); + + return true; + } + } + + /// + /// Rasterizes stroke edges in a single tile directly into the caller callback. + /// + private static void RasterizeStrokeSingleTileDirect( + ReadOnlySpan edges, + int width, + int height, + int interestTop, + int wordsPerRow, + int coverStride, + IntersectionRule intersectionRule, + RasterizationMode rasterizationMode, + float antialiasThreshold, + MemoryAllocator allocator, + RasterizerCoverageRowHandler rowHandler, + ref WorkerScratch? reusableScratch, + float halfWidth, + LineJoin lineJoin, + LineCap lineCap, + float miterLimit) + { + if (reusableScratch == null || !reusableScratch.CanReuse(wordsPerRow, coverStride, width, height)) + { + reusableScratch?.Dispose(); + reusableScratch = WorkerScratch.Create(allocator, wordsPerRow, coverStride, width, height); + } + + WorkerScratch scratch = reusableScratch; + Context context = scratch.CreateContext(height, intersectionRule, rasterizationMode, antialiasThreshold); + context.RasterizeStrokeEdges(edges, bandTop: 0, halfWidth, lineJoin, lineCap, miterLimit); + context.EmitCoverageRows(interestTop, scratch.Scanline, rowHandler); + context.ResetTouchedRows(); + } + + /// + /// Builds a band-sorted edge buffer where edges are duplicated into each band they touch. + /// Band offsets provide direct indexing — no per-band edge index array is needed. + /// + private static bool TryBuildBandSortedEdges( + ReadOnlySpan edges, + int bucketCount, + int bucketHeight, + MemoryAllocator allocator, + out IMemoryOwner sortedEdgesOwner, + out IMemoryOwner offsetsOwner) + { + using IMemoryOwner countsOwner = allocator.Allocate(bucketCount, AllocationOptions.Clean); + Span counts = countsOwner.Memory.Span; + long totalRefs = 0; + for (int i = 0; i < edges.Length; i++) + { + ref readonly EdgeData edge = ref edges[i]; + int minRow = Math.Min(edge.Y0, edge.Y1) >> FixedShift; + int maxRow = (Math.Max(edge.Y0, edge.Y1) - 1) >> FixedShift; + int startBucket = minRow / bucketHeight; + int endBucket = maxRow / bucketHeight; + totalRefs += (endBucket - startBucket) + 1; + if (totalRefs > int.MaxValue) + { + sortedEdgesOwner = null!; + offsetsOwner = null!; + return false; + } + + for (int b = startBucket; b <= endBucket; b++) + { + counts[b]++; + } + } + + int totalEdges = (int)totalRefs; + offsetsOwner = allocator.Allocate(bucketCount + 1); + Span offsets = offsetsOwner.Memory.Span; + int offset = 0; + for (int b = 0; b < bucketCount; b++) + { + offsets[b] = offset; + offset += counts[b]; + } + + offsets[bucketCount] = offset; + using IMemoryOwner writeCursorOwner = allocator.Allocate(bucketCount); + Span writeCursor = writeCursorOwner.Memory.Span; + offsets[..bucketCount].CopyTo(writeCursor); + + sortedEdgesOwner = allocator.Allocate(totalEdges); + Span sorted = sortedEdgesOwner.Memory.Span; + for (int i = 0; i < edges.Length; i++) + { + ref readonly EdgeData edge = ref edges[i]; + int minRow = Math.Min(edge.Y0, edge.Y1) >> FixedShift; + int maxRow = (Math.Max(edge.Y0, edge.Y1) - 1) >> FixedShift; + int startBucket = minRow / bucketHeight; + int endBucket = maxRow / bucketHeight; + for (int b = startBucket; b <= endBucket; b++) + { + sorted[writeCursor[b]++] = edge; + } + } + + return true; + } + + /// + /// Builds a band-sorted stroke edge buffer where descriptors are duplicated into each band + /// their stroke expansion could touch. + /// + private static bool TryBuildBandSortedStrokeEdges( + ReadOnlySpan edges, + int bucketCount, + int bucketHeight, + float expansion, + MemoryAllocator allocator, + out IMemoryOwner sortedEdgesOwner, + out IMemoryOwner offsetsOwner) + { + using IMemoryOwner countsOwner = allocator.Allocate(bucketCount, AllocationOptions.Clean); + Span counts = countsOwner.Memory.Span; + long totalRefs = 0; + for (int i = 0; i < edges.Length; i++) + { + ref readonly StrokeEdgeData edge = ref edges[i]; + + // Side edges: outline extends halfWidth from both endpoints. + // Join/cap edges: geometry is centered on vertex (X0,Y0), extends by expansion. + float minY, maxY; + if (edge.Flags == StrokeEdgeFlags.None) + { + minY = MathF.Min(edge.Y0, edge.Y1) - expansion; + maxY = MathF.Max(edge.Y0, edge.Y1) + expansion; + } + else + { + minY = edge.Y0 - expansion; + maxY = edge.Y0 + expansion; + } + + int startBucket = Math.Max(0, (int)MathF.Floor(minY / bucketHeight)); + int endBucket = Math.Min(bucketCount - 1, (int)MathF.Floor(maxY / bucketHeight)); + if (startBucket > endBucket) + { + continue; + } + + totalRefs += (endBucket - startBucket) + 1; + if (totalRefs > int.MaxValue) + { + sortedEdgesOwner = null!; + offsetsOwner = null!; + return false; + } + + for (int b = startBucket; b <= endBucket; b++) + { + counts[b]++; + } + } + + int totalEdges = (int)totalRefs; + offsetsOwner = allocator.Allocate(bucketCount + 1); + Span offsets = offsetsOwner.Memory.Span; + int offset = 0; + for (int b = 0; b < bucketCount; b++) + { + offsets[b] = offset; + offset += counts[b]; + } + + offsets[bucketCount] = offset; + using IMemoryOwner writeCursorOwner = allocator.Allocate(bucketCount); + Span writeCursor = writeCursorOwner.Memory.Span; + offsets[..bucketCount].CopyTo(writeCursor); + + sortedEdgesOwner = allocator.Allocate(Math.Max(totalEdges, 1)); + Span sorted = sortedEdgesOwner.Memory.Span; + for (int i = 0; i < edges.Length; i++) + { + ref readonly StrokeEdgeData edge = ref edges[i]; + float minY, maxY; + if (edge.Flags == StrokeEdgeFlags.None) + { + minY = MathF.Min(edge.Y0, edge.Y1) - expansion; + maxY = MathF.Max(edge.Y0, edge.Y1) + expansion; + } + else + { + minY = edge.Y0 - expansion; + maxY = edge.Y0 + expansion; + } + + int startBucket = Math.Max(0, (int)MathF.Floor(minY / bucketHeight)); + int endBucket = Math.Min(bucketCount - 1, (int)MathF.Floor(maxY / bucketHeight)); + for (int b = startBucket; b <= endBucket; b++) + { + sorted[writeCursor[b]++] = edge; + } + } + + return true; + } + + /// + /// Builds a stroke edge table from flattened path contours. + /// Each contour produces side edge descriptors for segments, join descriptors at interior + /// vertices, and cap descriptors at endpoints of open contours. + /// + /// Flattened path contours with open/closed state. + /// Interest left in absolute coordinates. + /// Interest top in absolute coordinates. + /// Horizontal sampling offset. + /// Vertical sampling offset. + /// Destination span for stroke edge descriptors. + /// Number of valid descriptors written. + private static int BuildStrokeEdgeTable( + List contours, + float minX, + float minY, + float samplingOffsetX, + float samplingOffsetY, + Span destination) + { + int count = 0; + for (int c = 0; c < contours.Count; c++) + { + ISimplePath sp = contours[c]; + ReadOnlySpan pts = sp.Points.Span; + int n = pts.Length; + if (n < 2) + { + continue; + } + + bool isClosed = sp.IsClosed; + float offX = samplingOffsetX - minX; + float offY = samplingOffsetY - minY; + + if (isClosed) + { + // Side edges for all segments, including closing segment back to first vertex. + for (int i = 0; i < n; i++) + { + int j = (i + 1) % n; + destination[count++] = new StrokeEdgeData( + pts[i].X + offX, + pts[i].Y + offY, + pts[j].X + offX, + pts[j].Y + offY, + 0); + } + + // Join at each vertex. + for (int i = 0; i < n; i++) + { + int prev = (i - 1 + n) % n; + int next = (i + 1) % n; + destination[count++] = new StrokeEdgeData( + pts[i].X + offX, + pts[i].Y + offY, + pts[prev].X + offX, + pts[prev].Y + offY, + StrokeEdgeFlags.Join, + pts[next].X + offX, + pts[next].Y + offY); + } + } + else + { + // Side edges for all segments. + for (int i = 0; i < n - 1; i++) + { + destination[count++] = new StrokeEdgeData( + pts[i].X + offX, + pts[i].Y + offY, + pts[i + 1].X + offX, + pts[i + 1].Y + offY, + 0); + } + + // Interior joins. + for (int i = 1; i < n - 1; i++) + { + destination[count++] = new StrokeEdgeData( + pts[i].X + offX, + pts[i].Y + offY, + pts[i - 1].X + offX, + pts[i - 1].Y + offY, + StrokeEdgeFlags.Join, + pts[i + 1].X + offX, + pts[i + 1].Y + offY); + } + + // Start cap. + destination[count++] = new StrokeEdgeData( + pts[0].X + offX, + pts[0].Y + offY, + pts[1].X + offX, + pts[1].Y + offY, + StrokeEdgeFlags.CapStart); + + // End cap. + destination[count++] = new StrokeEdgeData( + pts[n - 1].X + offX, + pts[n - 1].Y + offY, + pts[n - 2].X + offX, + pts[n - 2].Y + offY, + StrokeEdgeFlags.CapEnd); + } + } + + return count; + } + + /// + /// Builds an edge table in scanner-local coordinates. + /// + /// Flattened path contours. + /// Interest left in absolute coordinates. + /// Interest top in absolute coordinates. + /// Interest height in pixels. + /// Horizontal sampling offset. + /// Vertical sampling offset. + /// Destination span for edge records. + /// Number of valid edge records written. + private static int BuildEdgeTable( + IReadOnlyList contours, + int minX, + int minY, + int height, + float samplingOffsetX, + float samplingOffsetY, + Span destination) + { + int count = 0; + for (int r = 0; r < contours.Count; r++) + { + ISimplePath contour = contours[r]; + ReadOnlySpan points = contour.Points.Span; + int segmentCount = contour.IsClosed ? points.Length : points.Length - 1; + for (int i = 0; i < segmentCount; i++) + { + PointF p0 = points[i]; + PointF p1 = points[i + 1 == points.Length ? 0 : i + 1]; + + float x0 = (p0.X - minX) + samplingOffsetX; + float y0 = (p0.Y - minY) + samplingOffsetY; + float x1 = (p1.X - minX) + samplingOffsetX; + float y1 = (p1.Y - minY) + samplingOffsetY; + + if (!float.IsFinite(x0) || !float.IsFinite(y0) || !float.IsFinite(x1) || !float.IsFinite(y1)) + { + continue; + } + + if (!ClipToVerticalBounds(ref x0, ref y0, ref x1, ref y1, 0F, height)) + { + continue; + } + + int fx0 = FloatToFixed24Dot8(x0); + int fy0 = FloatToFixed24Dot8(y0); + int fx1 = FloatToFixed24Dot8(x1); + int fy1 = FloatToFixed24Dot8(y1); + if (fy0 == fy1) + { + continue; + } + + destination[count++] = new EdgeData(fx0, fy0, fx1, fy1); + } + } + + return count; + } + + /// + /// Converts bit count to the number of machine words needed to hold the bitset row. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int BitVectorsForMaxBitCount(int maxBitCount) => (maxBitCount + WordBitCount - 1) / WordBitCount; + + /// + /// Calculates the maximum reusable band height under memory and indexing constraints. + /// + /// Interest width. + /// Interest height. + /// Bitset words per row. + /// Cover-area stride in ints. + /// Resulting maximum safe band height. + /// when a valid band height was produced. + private static bool TryGetBandHeight(int width, int height, int wordsPerRow, long coverStride, out int bandHeight) + { + bandHeight = 0; + if (width <= 0 || height <= 0 || wordsPerRow <= 0 || coverStride <= 0) + { + return false; + } + + long bytesPerRow = + ((long)wordsPerRow * nint.Size) + + (coverStride * sizeof(int)) + + sizeof(int); + + long rowsByBudget = BandMemoryBudgetBytes / bytesPerRow; + if (rowsByBudget < 1) + { + rowsByBudget = 1; + } + + long rowsByBitVectors = int.MaxValue / wordsPerRow; + long rowsByCoverArea = int.MaxValue / coverStride; + long maxRows = Math.Min(rowsByBudget, Math.Min(rowsByBitVectors, rowsByCoverArea)); + if (maxRows < 1) + { + return false; + } + + bandHeight = (int)Math.Min(height, maxRows); + return bandHeight > 0; + } + + /// + /// Converts a float coordinate to signed 24.8 fixed-point. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FloatToFixed24Dot8(float value) => (int)MathF.Round(value * FixedOne); + + /// + /// Clips a fixed-point segment against vertical bounds. + /// + /// Segment start X in 24.8 fixed-point (updated in place). + /// Segment start Y in 24.8 fixed-point (updated in place). + /// Segment end X in 24.8 fixed-point (updated in place). + /// Segment end Y in 24.8 fixed-point (updated in place). + /// Minimum Y bound in 24.8 fixed-point. + /// Maximum Y bound in 24.8 fixed-point. + /// when a non-horizontal clipped segment remains. + private static bool ClipToVerticalBoundsFixed(ref int x0, ref int y0, ref int x1, ref int y1, int minY, int maxY) + { + double t0 = 0D; + double t1 = 1D; + int originX0 = x0; + int originY0 = y0; + long dx = (long)x1 - originX0; + long dy = (long)y1 - originY0; + if (!ClipTestFixed(-(double)dy, originY0 - (double)minY, ref t0, ref t1)) + { + return false; + } + + if (!ClipTestFixed(dy, maxY - (double)originY0, ref t0, ref t1)) + { + return false; + } + + if (t1 < 1D) + { + x1 = originX0 + (int)Math.Round(dx * t1); + y1 = originY0 + (int)Math.Round(dy * t1); + } + + if (t0 > 0D) + { + x0 = originX0 + (int)Math.Round(dx * t0); + y0 = originY0 + (int)Math.Round(dy * t0); + } + + return y0 != y1; + } + + /// + /// Clips a segment against vertical bounds using Liang-Barsky style parametric tests. + /// + /// Segment start X (updated in place). + /// Segment start Y (updated in place). + /// Segment end X (updated in place). + /// Segment end Y (updated in place). + /// Minimum Y bound. + /// Maximum Y bound. + /// when a non-horizontal clipped segment remains. + private static bool ClipToVerticalBounds(ref float x0, ref float y0, ref float x1, ref float y1, float minY, float maxY) + { + float t0 = 0F; + float t1 = 1F; + float dx = x1 - x0; + float dy = y1 - y0; + + if (!ClipTest(-dy, y0 - minY, ref t0, ref t1)) + { + return false; + } + + if (!ClipTest(dy, maxY - y0, ref t0, ref t1)) + { + return false; + } + + if (t1 < 1F) + { + x1 = x0 + (dx * t1); + y1 = y0 + (dy * t1); + } + + if (t0 > 0F) + { + x0 += dx * t0; + y0 += dy * t0; + } + + return y0 != y1; + } + + /// + /// One Liang-Barsky clip test step. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ClipTest(float p, float q, ref float t0, ref float t1) + { + if (p == 0F) + { + return q >= 0F; + } + + float r = q / p; + if (p < 0F) + { + if (r > t1) + { + return false; + } + + if (r > t0) + { + t0 = r; + } + } + else + { + if (r < t0) + { + return false; + } + + if (r < t1) + { + t1 = r; + } + } + + return true; + } + + /// + /// One Liang-Barsky clip test step for fixed-point clipping. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ClipTestFixed(double p, double q, ref double t0, ref double t1) + { + if (p == 0D) + { + return q >= 0D; + } + + double r = q / p; + if (p < 0D) + { + if (r > t1) + { + return false; + } + + if (r > t0) + { + t0 = r; + } + } + else + { + if (r < t0) + { + return false; + } + + if (r < t1) + { + t1 = r; + } + } + + return true; + } + + /// + /// Returns one when a fixed-point value lies exactly on a cell boundary at or below zero. + /// This is used to keep edge ownership consistent for vertical lines. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FindAdjustment(int value) + { + int lte0 = ~((value - 1) >> 31) & 1; + int divisibleBy256 = (((value & (FixedOne - 1)) - 1) >> 31) & 1; + return lte0 & divisibleBy256; + } + + /// + /// Machine-word trailing zero count used for sparse bitset iteration. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int TrailingZeroCount(nuint value) + => nint.Size == sizeof(ulong) + ? BitOperations.TrailingZeroCount((ulong)value) + : BitOperations.TrailingZeroCount((uint)value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInterestBoundsTooLarge() + => throw new ImageProcessingException("The rasterizer interest bounds are too large for DefaultRasterizer buffers."); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowBandHeightExceedsScratchCapacity() + => throw new ImageProcessingException("Requested band height exceeds worker scratch capacity."); + + /// + /// Band/tile-local scanner context that owns mutable coverage accumulation state. + /// + /// + /// Instances are intentionally stack-bound to keep hot-path data in spans and avoid heap churn. + /// + internal ref struct Context + { + private readonly Span bitVectors; + private readonly Span coverArea; + private readonly Span startCover; + private readonly Span rowMinTouchedColumn; + private readonly Span rowMaxTouchedColumn; + private readonly Span rowHasBits; + private readonly Span rowTouched; + private readonly Span touchedRows; + private readonly int width; + private readonly int height; + private readonly int wordsPerRow; + private readonly int coverStride; + private readonly IntersectionRule intersectionRule; + private readonly RasterizationMode rasterizationMode; + private readonly float antialiasThreshold; + private int touchedRowCount; + + /// + /// Initializes a new instance of the struct. + /// + public Context( + Span bitVectors, + Span coverArea, + Span startCover, + Span rowMinTouchedColumn, + Span rowMaxTouchedColumn, + Span rowHasBits, + Span rowTouched, + Span touchedRows, + int width, + int height, + int wordsPerRow, + int coverStride, + IntersectionRule intersectionRule, + RasterizationMode rasterizationMode, + float antialiasThreshold) + { + this.bitVectors = bitVectors; + this.coverArea = coverArea; + this.startCover = startCover; + this.rowMinTouchedColumn = rowMinTouchedColumn; + this.rowMaxTouchedColumn = rowMaxTouchedColumn; + this.rowHasBits = rowHasBits; + this.rowTouched = rowTouched; + this.touchedRows = touchedRows; + this.width = width; + this.height = height; + this.wordsPerRow = wordsPerRow; + this.coverStride = coverStride; + this.intersectionRule = intersectionRule; + this.rasterizationMode = rasterizationMode; + this.antialiasThreshold = antialiasThreshold; + this.touchedRowCount = 0; + } + + /// + /// Rasterizes all prebuilt edges that overlap this context. + /// + /// Shared edge table. + /// Top row of this context in global scanner-local coordinates. + public void RasterizeEdgeTable(ReadOnlySpan edges, int bandTop) + { + int bandTopFixed = bandTop * FixedOne; + int bandBottomFixed = bandTopFixed + (this.height * FixedOne); + + for (int i = 0; i < edges.Length; i++) + { + EdgeData edge = edges[i]; + int x0 = edge.X0; + int y0 = edge.Y0; + int x1 = edge.X1; + int y1 = edge.Y1; + + // Fast-path: edge is fully within this band — no clipping needed. + int minY = Math.Min(y0, y1); + int maxY = Math.Max(y0, y1); + if (minY >= bandTopFixed && maxY <= bandBottomFixed) + { + this.RasterizeLine(x0, y0 - bandTopFixed, x1, y1 - bandTopFixed); + continue; + } + + if (!ClipToVerticalBoundsFixed(ref x0, ref y0, ref x1, ref y1, bandTopFixed, bandBottomFixed)) + { + continue; + } + + // Convert global scanner Y to band-local Y after clipping. + y0 -= bandTopFixed; + y1 -= bandTopFixed; + + this.RasterizeLine(x0, y0, x1, y1); + } + } + + /// + /// Expands stroke centerline edge descriptors into outline polygon edges and rasterizes them. + /// + /// Band-sorted stroke edge descriptors. + /// Top row of this context in global scanner-local coordinates. + /// Half the stroke width in pixels. + /// Outer join style. + /// Cap style for open contour endpoints. + /// Miter limit for miter-family joins. + public void RasterizeStrokeEdges( + ReadOnlySpan edges, + int bandTop, + float halfWidth, + LineJoin lineJoin, + LineCap lineCap, + float miterLimit) + { + int bandTopFixed = bandTop * FixedOne; + int bandBottomFixed = bandTopFixed + (this.height * FixedOne); + + for (int i = 0; i < edges.Length; i++) + { + ref readonly StrokeEdgeData edge = ref edges[i]; + StrokeEdgeFlags flags = edge.Flags; + + if (flags == StrokeEdgeFlags.None) + { + this.ExpandSideEdge(in edge, halfWidth, bandTopFixed, bandBottomFixed); + } + else if ((flags & StrokeEdgeFlags.Join) != 0) + { + this.ExpandJoinEdge(in edge, halfWidth, lineJoin, miterLimit, bandTopFixed, bandBottomFixed); + } + else if ((flags & StrokeEdgeFlags.CapStart) != 0) + { + this.ExpandCapEdge(in edge, halfWidth, lineCap, isStart: true, bandTopFixed, bandBottomFixed); + } + else + { + this.ExpandCapEdge(in edge, halfWidth, lineCap, isStart: false, bandTopFixed, bandBottomFixed); + } + } + } + + /// + /// Emits one outline edge into the rasterizer, converting from float to fixed-point + /// and clipping to band bounds. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EmitOutlineEdge( + float ex0, + float ey0, + float ex1, + float ey1, + int bandTopFixed, + int bandBottomFixed) + { + int fy0 = FloatToFixed24Dot8(ey0); + int fy1 = FloatToFixed24Dot8(ey1); + if (fy0 == fy1) + { + return; + } + + int fx0 = FloatToFixed24Dot8(ex0); + int fx1 = FloatToFixed24Dot8(ex1); + + int minY = Math.Min(fy0, fy1); + int maxY = Math.Max(fy0, fy1); + if (minY >= bandBottomFixed || maxY <= bandTopFixed) + { + return; + } + + if (minY >= bandTopFixed && maxY <= bandBottomFixed) + { + this.RasterizeLine(fx0, fy0 - bandTopFixed, fx1, fy1 - bandTopFixed); + return; + } + + int x0 = fx0, y0 = fy0, x1 = fx1, y1 = fy1; + if (ClipToVerticalBoundsFixed(ref x0, ref y0, ref x1, ref y1, bandTopFixed, bandBottomFixed)) + { + this.RasterizeLine(x0, y0 - bandTopFixed, x1, y1 - bandTopFixed); + } + } + + /// + /// Expands a side (segment) edge into two outline edges offset by the stroke normal. + /// + private void ExpandSideEdge( + in StrokeEdgeData edge, + float halfWidth, + int bandTopFixed, + int bandBottomFixed) + { + float dx = edge.X1 - edge.X0; + float dy = edge.Y1 - edge.Y0; + float len = MathF.Sqrt((dx * dx) + (dy * dy)); + if (len < 1e-6f) + { + return; + } + + float nx = (-dy / len) * halfWidth; + float ny = (dx / len) * halfWidth; + + // Left side. + this.EmitOutlineEdge( + edge.X0 + nx, + edge.Y0 + ny, + edge.X1 + nx, + edge.Y1 + ny, + bandTopFixed, + bandBottomFixed); + + // Right side (reversed winding). + this.EmitOutlineEdge( + edge.X1 - nx, + edge.Y1 - ny, + edge.X0 - nx, + edge.Y0 - ny, + bandTopFixed, + bandBottomFixed); + } + + /// + /// Expands a join descriptor into inner bevel + outer join edges. + /// Ported from the GPU StrokeExpandComputeShader join logic. + /// + private void ExpandJoinEdge( + in StrokeEdgeData edge, + float halfWidth, + LineJoin lineJoin, + float miterLimit, + int bandTopFixed, + int bandBottomFixed) + { + float vx = edge.X0, vy = edge.Y0; + float dx1 = vx - edge.X1, dy1 = vy - edge.Y1; + float len1 = MathF.Sqrt((dx1 * dx1) + (dy1 * dy1)); + if (len1 < 1e-6f) + { + return; + } + + float dx2 = edge.AdjX - vx, dy2 = edge.AdjY - vy; + float len2 = MathF.Sqrt((dx2 * dx2) + (dy2 * dy2)); + if (len2 < 1e-6f) + { + return; + } + + float nx1 = -dy1 / len1, ny1 = dx1 / len1; + float nx2 = -dy2 / len2, ny2 = dx2 / len2; + float cross = (dx1 * dy2) - (dy1 * dx2); + + float oax, oay, obx, oby, iax, iay, ibx, iby; + if (cross > 0) + { + oax = vx - (nx1 * halfWidth); + oay = vy - (ny1 * halfWidth); + obx = vx - (nx2 * halfWidth); + oby = vy - (ny2 * halfWidth); + iax = vx + (nx1 * halfWidth); + iay = vy + (ny1 * halfWidth); + ibx = vx + (nx2 * halfWidth); + iby = vy + (ny2 * halfWidth); + } + else + { + oax = vx + (nx1 * halfWidth); + oay = vy + (ny1 * halfWidth); + obx = vx + (nx2 * halfWidth); + oby = vy + (ny2 * halfWidth); + iax = vx - (nx1 * halfWidth); + iay = vy - (ny1 * halfWidth); + ibx = vx - (nx2 * halfWidth); + iby = vy - (ny2 * halfWidth); + } + + float ofx, ofy, otx, oty, ifx, ify, itx, ity; + if (cross > 0) + { + ofx = obx; + ofy = oby; + otx = oax; + oty = oay; + ifx = iax; + ify = iay; + itx = ibx; + ity = iby; + } + else + { + ofx = oax; + ofy = oay; + otx = obx; + oty = oby; + ifx = ibx; + ify = iby; + itx = iax; + ity = iay; + } + + // Inner join: always bevel. + this.EmitOutlineEdge(ifx, ify, itx, ity, bandTopFixed, bandBottomFixed); + + // Outer join. + bool miterHandled = false; + if (lineJoin is LineJoin.Miter or LineJoin.MiterRevert or LineJoin.MiterRound) + { + float ux1 = dx1 / len1; + float uy1 = dy1 / len1; + float ux2 = dx2 / len2; + float uy2 = dy2 / len2; + float denom = (ux1 * uy2) - (uy1 * ux2); + if (MathF.Abs(denom) > 1e-4f) + { + float dpx = obx - oax; + float dpy = oby - oay; + float t = ((dpx * uy2) - (dpy * ux2)) / denom; + float mx = oax + (t * ux1); + float my = oay + (t * uy1); + float mdx = mx - vx; + float mdy = my - vy; + float miterDist = MathF.Sqrt((mdx * mdx) + (mdy * mdy)); + float limit = halfWidth * miterLimit; + if (miterDist <= limit) + { + this.EmitOutlineEdge(ofx, ofy, mx, my, bandTopFixed, bandBottomFixed); + this.EmitOutlineEdge(mx, my, otx, oty, bandTopFixed, bandBottomFixed); + miterHandled = true; + } + else if (lineJoin == LineJoin.Miter) + { + // Clipped miter: blend between bevel and full miter at the limit distance. + float bdx = ((oax + obx) * 0.5f) - vx; + float bdy = ((oay + oby) * 0.5f) - vy; + float bdist = MathF.Sqrt((bdx * bdx) + (bdy * bdy)); + float blend = Math.Clamp((limit - bdist) / (miterDist - bdist), 0f, 1f); + float cx1 = ofx + ((mx - ofx) * blend); + float cy1 = ofy + ((my - ofy) * blend); + float cx2 = otx + ((mx - otx) * blend); + float cy2 = oty + ((my - oty) * blend); + this.EmitOutlineEdge(ofx, ofy, cx1, cy1, bandTopFixed, bandBottomFixed); + this.EmitOutlineEdge(cx1, cy1, cx2, cy2, bandTopFixed, bandBottomFixed); + this.EmitOutlineEdge(cx2, cy2, otx, oty, bandTopFixed, bandBottomFixed); + miterHandled = true; + } + } + } + + if (!miterHandled) + { + if (lineJoin is LineJoin.Round or LineJoin.MiterRound) + { + float sa = MathF.Atan2(ofy - vy, ofx - vx); + float ea = MathF.Atan2(oty - vy, otx - vx); + float sweep = ea - sa; + if (sweep > MathF.PI) + { + sweep -= MathF.PI * 2f; + } + + if (sweep < -MathF.PI) + { + sweep += MathF.PI * 2f; + } + + int steps = Math.Max(4, (int)MathF.Ceiling(MathF.Abs(sweep) * halfWidth * 0.5f)); + float da = sweep / steps; + float pax = ofx; + float pay = ofy; + for (int s = 1; s <= steps; s++) + { + float cax, cay; + if (s == steps) + { + cax = otx; + cay = oty; + } + else + { + float a = sa + (da * s); + cax = vx + (MathF.Cos(a) * halfWidth); + cay = vy + (MathF.Sin(a) * halfWidth); + } + + this.EmitOutlineEdge(pax, pay, cax, cay, bandTopFixed, bandBottomFixed); + pax = cax; + pay = cay; + } + } + else + { + // Bevel. + this.EmitOutlineEdge(ofx, ofy, otx, oty, bandTopFixed, bandBottomFixed); + } + } + } + + /// + /// Expands a cap descriptor into cap geometry edges (butt, square, or round). + /// Ported from the GPU StrokeExpandComputeShader cap logic. + /// + private void ExpandCapEdge( + in StrokeEdgeData edge, + float halfWidth, + LineCap lineCap, + bool isStart, + int bandTopFixed, + int bandBottomFixed) + { + float cx = edge.X0; + float cy = edge.Y0; + float ax = edge.X1; + float ay = edge.Y1; + float dx, dy; + if (isStart) + { + dx = ax - cx; + dy = ay - cy; + } + else + { + dx = cx - ax; + dy = cy - ay; + } + + float len = MathF.Sqrt((dx * dx) + (dy * dy)); + if (len < 1e-6f) + { + return; + } + + float dirX = dx / len; + float dirY = dy / len; + float nx = -dirY * halfWidth; + float ny = dirX * halfWidth; + float lx = cx + nx; + float ly = cy + ny; + float rx = cx - nx; + float ry = cy - ny; + + if (lineCap == LineCap.Butt) + { + if (isStart) + { + this.EmitOutlineEdge(rx, ry, lx, ly, bandTopFixed, bandBottomFixed); + } + else + { + this.EmitOutlineEdge(lx, ly, rx, ry, bandTopFixed, bandBottomFixed); + } + } + else if (lineCap == LineCap.Square) + { + float ox, oy; + if (isStart) + { + ox = -dirX * halfWidth; + oy = -dirY * halfWidth; + } + else + { + ox = dirX * halfWidth; + oy = dirY * halfWidth; + } + + float lxe = lx + ox; + float lye = ly + oy; + float rxe = rx + ox; + float rye = ry + oy; + + if (isStart) + { + this.EmitOutlineEdge(rx, ry, rxe, rye, bandTopFixed, bandBottomFixed); + this.EmitOutlineEdge(rxe, rye, lxe, lye, bandTopFixed, bandBottomFixed); + this.EmitOutlineEdge(lxe, lye, lx, ly, bandTopFixed, bandBottomFixed); + } + else + { + this.EmitOutlineEdge(lx, ly, lxe, lye, bandTopFixed, bandBottomFixed); + this.EmitOutlineEdge(lxe, lye, rxe, rye, bandTopFixed, bandBottomFixed); + this.EmitOutlineEdge(rxe, rye, rx, ry, bandTopFixed, bandBottomFixed); + } + } + else + { + // Round cap. + float sa, sx, sy, ex, ey; + if (isStart) + { + sa = MathF.Atan2(ry - cy, rx - cx); + sx = rx; + sy = ry; + ex = lx; + ey = ly; + } + else + { + sa = MathF.Atan2(ly - cy, lx - cx); + sx = lx; + sy = ly; + ex = rx; + ey = ry; + } + + float sweep = MathF.Atan2(ey - cy, ex - cx) - sa; + if (sweep > 0f) + { + sweep -= MathF.PI * 2f; + } + + int steps = Math.Max(4, (int)MathF.Ceiling(MathF.Abs(sweep) * halfWidth * 0.5f)); + float da = sweep / steps; + float pax = sx; + float pay = sy; + for (int s = 1; s <= steps; s++) + { + float cax, cay; + if (s == steps) + { + cax = ex; + cay = ey; + } + else + { + float a = sa + (da * s); + cax = cx + (MathF.Cos(a) * halfWidth); + cay = cy + (MathF.Sin(a) * halfWidth); + } + + this.EmitOutlineEdge(pax, pay, cax, cay, bandTopFixed, bandBottomFixed); + pax = cax; + pay = cay; + } + } + } + + /// + /// Converts accumulated cover/area tables into non-zero coverage span callbacks. + /// + /// Absolute destination Y corresponding to row zero in this context. + /// Reusable scanline scratch buffer used to materialize emitted spans. + /// Coverage callback invoked for each emitted non-zero span. + public readonly void EmitCoverageRows(int destinationTop, Span scanline, RasterizerCoverageRowHandler rowHandler) + { + // Iterate only rows that actually received coverage contributions. + // MarkRowTouched is called from AddCell for all contributions, including + // column-less startCover accumulations, so touchedRows is complete. + for (int i = 0; i < this.touchedRowCount; i++) + { + int row = this.touchedRows[i]; + int rowCover = this.startCover[row]; + bool rowHasBits = this.rowHasBits[row] != 0; + if (rowCover == 0 && !rowHasBits) + { + // Safety guard — should not fire in practice. + continue; + } + + if (!rowHasBits) + { + // No touched cells in this row, but carry cover from x < 0 can still + // produce a full-width constant span. + float coverage = this.AreaToCoverage(rowCover << AreaToCoverageShift); + if (coverage > 0F) + { + scanline[..this.width].Fill(coverage); + rowHandler(destinationTop + row, 0, scanline[..this.width]); + } + + continue; + } + + int minTouchedColumn = this.rowMinTouchedColumn[row]; + int maxTouchedColumn = this.rowMaxTouchedColumn[row]; + ReadOnlySpan rowBitVectors = this.bitVectors.Slice(row * this.wordsPerRow, this.wordsPerRow); + this.EmitRowCoverage( + rowBitVectors, + row, + rowCover, + minTouchedColumn, + maxTouchedColumn, + destinationTop + row, + scanline, + rowHandler); + } + } + + /// + /// Clears only rows touched during the previous rasterization pass. + /// + /// + /// This sparse reset strategy avoids clearing full scratch buffers when geometry is sparse. + /// + public void ResetTouchedRows() + { + // Reset only rows that received contributions in this band. This avoids clearing + // full temporary buffers when geometry is sparse relative to the interest bounds. + for (int i = 0; i < this.touchedRowCount; i++) + { + int row = this.touchedRows[i]; + this.startCover[row] = 0; + this.rowTouched[row] = 0; + + if (this.rowHasBits[row] == 0) + { + continue; + } + + this.rowHasBits[row] = 0; + + // Clear only touched bitset words for this row. + int minWord = this.rowMinTouchedColumn[row] / WordBitCount; + int maxWord = this.rowMaxTouchedColumn[row] / WordBitCount; + int wordCount = (maxWord - minWord) + 1; + this.bitVectors.Slice((row * this.wordsPerRow) + minWord, wordCount).Clear(); + } + + this.touchedRowCount = 0; + } + + /// + /// Emits one row by iterating touched columns and coalescing equal-coverage spans. + /// + /// Bitset words indicating touched columns in this row. + /// Row index inside the context. + /// Initial carry cover value from x less than zero contributions. + /// Minimum touched column index in this row. + /// Maximum touched column index in this row. + /// Absolute destination y for this row. + /// Reusable scanline coverage buffer used for per-span materialization. + /// Coverage callback invoked for each emitted non-zero span. + private readonly void EmitRowCoverage( + ReadOnlySpan rowBitVectors, + int row, + int cover, + int minTouchedColumn, + int maxTouchedColumn, + int destinationY, + Span scanline, + RasterizerCoverageRowHandler rowHandler) + { + int rowOffset = row * this.coverStride; + int spanStart = 0; + int spanEnd = 0; + float spanCoverage = 0F; + int runStart = -1; + int runEnd = -1; + int minWord = minTouchedColumn / WordBitCount; + int maxWord = maxTouchedColumn / WordBitCount; + + for (int wordIndex = minWord; wordIndex <= maxWord; wordIndex++) + { + // Iterate touched columns sparsely by scanning set bits only. + nuint bitset = rowBitVectors[wordIndex]; + while (bitset != 0) + { + int localBitIndex = TrailingZeroCount(bitset); + bitset &= bitset - 1; + + int x = (wordIndex * WordBitCount) + localBitIndex; + if ((uint)x >= (uint)this.width) + { + continue; + } + + int tableIndex = rowOffset + (x << 1); + + // Area uses current cover before adding this cell's delta. This matches + // scan-conversion math where area integrates the edge state at cell entry. + int area = this.coverArea[tableIndex + 1] + (cover << AreaToCoverageShift); + float coverage = this.AreaToCoverage(area); + + if (spanEnd == x) + { + if (coverage <= 0F) + { + WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd); + EmitRun(rowHandler, destinationY, scanline, ref runStart, ref runEnd); + spanStart = x + 1; + spanEnd = spanStart; + spanCoverage = 0F; + } + else if (coverage == spanCoverage) + { + spanEnd = x + 1; + } + else + { + WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd); + spanStart = x; + spanEnd = x + 1; + spanCoverage = coverage; + } + } + else + { + // We jumped over untouched columns. If cover != 0 the gap has a constant + // non-zero coverage and must be emitted as its own run. + if (cover == 0) + { + WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd); + EmitRun(rowHandler, destinationY, scanline, ref runStart, ref runEnd); + spanStart = x; + spanEnd = x + 1; + spanCoverage = coverage; + } + else + { + float gapCoverage = this.AreaToCoverage(cover << AreaToCoverageShift); + if (gapCoverage <= 0F) + { + // Even-odd can map non-zero winding to zero coverage. + // Treat this as a hard run break so we don't bridge holes. + WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd); + EmitRun(rowHandler, destinationY, scanline, ref runStart, ref runEnd); + spanStart = x; + spanEnd = x + 1; + spanCoverage = coverage; + } + else if (spanCoverage == gapCoverage) + { + if (coverage == gapCoverage) + { + spanEnd = x + 1; + } + else + { + WriteSpan(scanline, spanStart, x, spanCoverage, ref runStart, ref runEnd); + spanStart = x; + spanEnd = x + 1; + spanCoverage = coverage; + } + } + else + { + WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd); + WriteSpan(scanline, spanEnd, x, gapCoverage, ref runStart, ref runEnd); + spanStart = x; + spanEnd = x + 1; + spanCoverage = coverage; + } + } + } + + cover += this.coverArea[tableIndex]; + } + } + + // Flush tail run and any remaining constant-cover tail after the last touched cell. + WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd); + if (cover != 0 && spanEnd < this.width) + { + WriteSpan(scanline, spanEnd, this.width, this.AreaToCoverage(cover << AreaToCoverageShift), ref runStart, ref runEnd); + } + + EmitRun(rowHandler, destinationY, scanline, ref runStart, ref runEnd); + } + + /// + /// Converts accumulated signed area to normalized coverage under the selected fill rule. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly float AreaToCoverage(int area) + { + int signedArea = area >> AreaToCoverageShift; + int absoluteArea = signedArea < 0 ? -signedArea : signedArea; + float coverage; + if (this.intersectionRule == IntersectionRule.NonZero) + { + // Non-zero winding clamps absolute winding accumulation to [0, 1]. + if (absoluteArea >= CoverageStepCount) + { + coverage = 1F; + } + else + { + coverage = absoluteArea * CoverageScale; + } + } + else + { + // Even-odd wraps every 2*CoverageStepCount and mirrors second half. + int wrapped = absoluteArea & EvenOddMask; + if (wrapped > CoverageStepCount) + { + wrapped = EvenOddPeriod - wrapped; + } + + coverage = wrapped >= CoverageStepCount ? 1F : wrapped * CoverageScale; + } + + if (this.rasterizationMode == RasterizationMode.Aliased) + { + // Aliased mode quantizes final coverage to hard 0/1 per pixel + // using the configurable threshold from GraphicsOptions.AntialiasThreshold. + return coverage >= this.antialiasThreshold ? 1F : 0F; + } + + return coverage; + } + + /// + /// Writes one non-zero coverage segment into the scanline and expands the active run. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteSpan( + Span scanline, + int start, + int end, + float coverage, + ref int runStart, + ref int runEnd) + { + if (coverage <= 0F || end <= start) + { + return; + } + + scanline[start..end].Fill(coverage); + if (runStart < 0) + { + runStart = start; + runEnd = end; + return; + } + + if (end > runEnd) + { + runEnd = end; + } + } + + /// + /// Emits the currently accumulated non-zero run, if any. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EmitRun( + RasterizerCoverageRowHandler rowHandler, + int destinationY, + Span scanline, + ref int runStart, + ref int runEnd) + { + if (runStart < 0) + { + return; + } + + rowHandler(destinationY, runStart, scanline[runStart..runEnd]); + runStart = -1; + runEnd = -1; + } + + /// + /// Sets a row/column bit and reports whether it was newly set. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly bool ConditionalSetBit(int row, int column, out bool rowHadBits) + { + int bitIndex = row * this.wordsPerRow; + int wordIndex = bitIndex + (column / WordBitCount); + nuint mask = (nuint)1 << (column % WordBitCount); + ref nuint word = ref this.bitVectors[wordIndex]; + bool newlySet = (word & mask) == 0; + word |= mask; + + // Single read of rowHasBits serves both the conditional store + // and the caller's min/max column tracking. + rowHadBits = this.rowHasBits[row] != 0; + if (!rowHadBits) + { + this.rowHasBits[row] = 1; + } + + return newlySet; + } + + /// + /// Adds one cell contribution into cover/area accumulators. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddCell(int row, int column, int delta, int area) + { + if ((uint)row >= (uint)this.height) + { + return; + } + + this.MarkRowTouched(row); + + if (column < 0) + { + // Contributions left of x=0 accumulate into the row carry. + this.startCover[row] += delta; + return; + } + + if ((uint)column >= (uint)this.width) + { + return; + } + + int index = (row * this.coverStride) + (column << 1); + if (this.ConditionalSetBit(row, column, out bool rowHadBits)) + { + // First write wins initialization path avoids reading old values. + this.coverArea[index] = delta; + this.coverArea[index + 1] = area; + } + else + { + // Multiple edges can hit the same cell; accumulate signed values. + this.coverArea[index] += delta; + this.coverArea[index + 1] += area; + } + + if (!rowHadBits) + { + this.rowMinTouchedColumn[row] = column; + this.rowMaxTouchedColumn[row] = column; + } + else + { + if (column < this.rowMinTouchedColumn[row]) + { + this.rowMinTouchedColumn[row] = column; + } + + if (column > this.rowMaxTouchedColumn[row]) + { + this.rowMaxTouchedColumn[row] = column; + } + } + } + + /// + /// Marks a row as touched once so sparse reset can clear it later. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void MarkRowTouched(int row) + { + if (this.rowTouched[row] != 0) + { + return; + } + + this.rowTouched[row] = 1; + this.touchedRows[this.touchedRowCount++] = row; + } + + /// + /// Emits one vertical cell contribution. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CellVertical(int px, int py, int x, int y0, int y1) + { + int delta = y0 - y1; + int area = delta * ((FixedOne * 2) - x - x); + this.AddCell(py, px, delta, area); + } + + /// + /// Emits one general cell contribution. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Cell(int row, int px, int x0, int y0, int x1, int y1) + { + int delta = y0 - y1; + int area = delta * ((FixedOne * 2) - x0 - x1); + this.AddCell(row, px, delta, area); + } + + /// + /// Rasterizes a downward vertical edge segment. + /// + private void VerticalDown(int columnIndex, int y0, int y1, int x) + { + int rowIndex0 = y0 >> FixedShift; + int rowIndex1 = (y1 - 1) >> FixedShift; + int fy0 = y0 - (rowIndex0 << FixedShift); + int fy1 = y1 - (rowIndex1 << FixedShift); + int fx = x - (columnIndex << FixedShift); + + if (rowIndex0 == rowIndex1) + { + // Entire segment stays within one row. + this.CellVertical(columnIndex, rowIndex0, fx, fy0, fy1); + return; + } + + // First partial row, full middle rows, last partial row. + this.CellVertical(columnIndex, rowIndex0, fx, fy0, FixedOne); + for (int row = rowIndex0 + 1; row < rowIndex1; row++) + { + this.CellVertical(columnIndex, row, fx, 0, FixedOne); + } + + this.CellVertical(columnIndex, rowIndex1, fx, 0, fy1); + } + + /// + /// Rasterizes an upward vertical edge segment. + /// + private void VerticalUp(int columnIndex, int y0, int y1, int x) + { + int rowIndex0 = (y0 - 1) >> FixedShift; + int rowIndex1 = y1 >> FixedShift; + int fy0 = y0 - (rowIndex0 << FixedShift); + int fy1 = y1 - (rowIndex1 << FixedShift); + int fx = x - (columnIndex << FixedShift); + + if (rowIndex0 == rowIndex1) + { + // Entire segment stays within one row. + this.CellVertical(columnIndex, rowIndex0, fx, fy0, fy1); + return; + } + + // First partial row, full middle rows, last partial row (upward direction). + this.CellVertical(columnIndex, rowIndex0, fx, fy0, 0); + for (int row = rowIndex0 - 1; row > rowIndex1; row--) + { + this.CellVertical(columnIndex, row, fx, FixedOne, 0); + } + + this.CellVertical(columnIndex, rowIndex1, fx, FixedOne, fy1); + } + + // The following row/line helpers are directional variants of the same fixed-point edge + // walker. They are intentionally split to minimize branch costs in hot loops. + + /// + /// Rasterizes a downward, left-to-right segment within a single row. + /// + private void RowDownR(int rowIndex, int p0x, int p0y, int p1x, int p1y) + { + int columnIndex0 = p0x >> FixedShift; + int columnIndex1 = (p1x - 1) >> FixedShift; + int fx0 = p0x - (columnIndex0 << FixedShift); + int fx1 = p1x - (columnIndex1 << FixedShift); + + if (columnIndex0 == columnIndex1) + { + this.Cell(rowIndex, columnIndex0, fx0, p0y, fx1, p1y); + return; + } + + int dx = p1x - p0x; + int dy = p1y - p0y; + int pp = (FixedOne - fx0) * dy; + int cy = p0y + (pp / dx); + + this.Cell(rowIndex, columnIndex0, fx0, p0y, FixedOne, cy); + + int idx = columnIndex0 + 1; + if (idx != columnIndex1) + { + int mod = (pp % dx) - dx; + int p = FixedOne * dy; + int lift = p / dx; + int rem = p % dx; + + for (; idx != columnIndex1; idx++) + { + int delta = lift; + mod += rem; + if (mod >= 0) + { + mod -= dx; + delta++; + } + + int ny = cy + delta; + this.Cell(rowIndex, idx, 0, cy, FixedOne, ny); + cy = ny; + } + } + + this.Cell(rowIndex, columnIndex1, 0, cy, fx1, p1y); + } + + /// + /// RowDownR variant that handles perfectly vertical edge ownership consistently. + /// + private void RowDownR_V(int rowIndex, int p0x, int p0y, int p1x, int p1y) + { + if (p0x < p1x) + { + this.RowDownR(rowIndex, p0x, p0y, p1x, p1y); + } + else + { + int columnIndex = (p0x - FindAdjustment(p0x)) >> FixedShift; + int x = p0x - (columnIndex << FixedShift); + this.CellVertical(columnIndex, rowIndex, x, p0y, p1y); + } + } + + /// + /// Rasterizes an upward, left-to-right segment within a single row. + /// + private void RowUpR(int rowIndex, int p0x, int p0y, int p1x, int p1y) + { + int columnIndex0 = p0x >> FixedShift; + int columnIndex1 = (p1x - 1) >> FixedShift; + int fx0 = p0x - (columnIndex0 << FixedShift); + int fx1 = p1x - (columnIndex1 << FixedShift); + + if (columnIndex0 == columnIndex1) + { + this.Cell(rowIndex, columnIndex0, fx0, p0y, fx1, p1y); + return; + } + + int dx = p1x - p0x; + int dy = p0y - p1y; + int pp = (FixedOne - fx0) * dy; + int cy = p0y - (pp / dx); + + this.Cell(rowIndex, columnIndex0, fx0, p0y, FixedOne, cy); + + int idx = columnIndex0 + 1; + if (idx != columnIndex1) + { + int mod = (pp % dx) - dx; + int p = FixedOne * dy; + int lift = p / dx; + int rem = p % dx; + + for (; idx != columnIndex1; idx++) + { + int delta = lift; + mod += rem; + if (mod >= 0) + { + mod -= dx; + delta++; + } + + int ny = cy - delta; + this.Cell(rowIndex, idx, 0, cy, FixedOne, ny); + cy = ny; + } + } + + this.Cell(rowIndex, columnIndex1, 0, cy, fx1, p1y); + } + + /// + /// RowUpR variant that handles perfectly vertical edge ownership consistently. + /// + private void RowUpR_V(int rowIndex, int p0x, int p0y, int p1x, int p1y) + { + if (p0x < p1x) + { + this.RowUpR(rowIndex, p0x, p0y, p1x, p1y); + } + else + { + int columnIndex = (p0x - FindAdjustment(p0x)) >> FixedShift; + int x = p0x - (columnIndex << FixedShift); + this.CellVertical(columnIndex, rowIndex, x, p0y, p1y); + } + } + + /// + /// Rasterizes a downward, right-to-left segment within a single row. + /// + private void RowDownL(int rowIndex, int p0x, int p0y, int p1x, int p1y) + { + int columnIndex0 = (p0x - 1) >> FixedShift; + int columnIndex1 = p1x >> FixedShift; + int fx0 = p0x - (columnIndex0 << FixedShift); + int fx1 = p1x - (columnIndex1 << FixedShift); + + if (columnIndex0 == columnIndex1) + { + this.Cell(rowIndex, columnIndex0, fx0, p0y, fx1, p1y); + return; + } + + int dx = p0x - p1x; + int dy = p1y - p0y; + int pp = fx0 * dy; + int cy = p0y + (pp / dx); + + this.Cell(rowIndex, columnIndex0, fx0, p0y, 0, cy); + + int idx = columnIndex0 - 1; + if (idx != columnIndex1) + { + int mod = (pp % dx) - dx; + int p = FixedOne * dy; + int lift = p / dx; + int rem = p % dx; + + for (; idx != columnIndex1; idx--) + { + int delta = lift; + mod += rem; + if (mod >= 0) + { + mod -= dx; + delta++; + } + + int ny = cy + delta; + this.Cell(rowIndex, idx, FixedOne, cy, 0, ny); + cy = ny; + } + } + + this.Cell(rowIndex, columnIndex1, FixedOne, cy, fx1, p1y); + } + + /// + /// RowDownL variant that handles perfectly vertical edge ownership consistently. + /// + private void RowDownL_V(int rowIndex, int p0x, int p0y, int p1x, int p1y) + { + if (p0x > p1x) + { + this.RowDownL(rowIndex, p0x, p0y, p1x, p1y); + } + else + { + int columnIndex = (p0x - FindAdjustment(p0x)) >> FixedShift; + int x = p0x - (columnIndex << FixedShift); + this.CellVertical(columnIndex, rowIndex, x, p0y, p1y); + } + } + + /// + /// Rasterizes an upward, right-to-left segment within a single row. + /// + private void RowUpL(int rowIndex, int p0x, int p0y, int p1x, int p1y) + { + int columnIndex0 = (p0x - 1) >> FixedShift; + int columnIndex1 = p1x >> FixedShift; + int fx0 = p0x - (columnIndex0 << FixedShift); + int fx1 = p1x - (columnIndex1 << FixedShift); + + if (columnIndex0 == columnIndex1) + { + this.Cell(rowIndex, columnIndex0, fx0, p0y, fx1, p1y); + return; + } + + int dx = p0x - p1x; + int dy = p0y - p1y; + int pp = fx0 * dy; + int cy = p0y - (pp / dx); + + this.Cell(rowIndex, columnIndex0, fx0, p0y, 0, cy); + + int idx = columnIndex0 - 1; + if (idx != columnIndex1) + { + int mod = (pp % dx) - dx; + int p = FixedOne * dy; + int lift = p / dx; + int rem = p % dx; + + for (; idx != columnIndex1; idx--) + { + int delta = lift; + mod += rem; + if (mod >= 0) + { + mod -= dx; + delta++; + } + + int ny = cy - delta; + this.Cell(rowIndex, idx, FixedOne, cy, 0, ny); + cy = ny; + } + } + + this.Cell(rowIndex, columnIndex1, FixedOne, cy, fx1, p1y); + } + + /// + /// RowUpL variant that handles perfectly vertical edge ownership consistently. + /// + private void RowUpL_V(int rowIndex, int p0x, int p0y, int p1x, int p1y) + { + if (p0x > p1x) + { + this.RowUpL(rowIndex, p0x, p0y, p1x, p1y); + } + else + { + int columnIndex = (p0x - FindAdjustment(p0x)) >> FixedShift; + int x = p0x - (columnIndex << FixedShift); + this.CellVertical(columnIndex, rowIndex, x, p0y, p1y); + } + } + + /// + /// Rasterizes a downward, left-to-right segment spanning multiple rows. + /// + private void LineDownR(int rowIndex0, int rowIndex1, int x0, int y0, int x1, int y1) + { + int dx = x1 - x0; + int dy = y1 - y0; + int fy0 = y0 - (rowIndex0 << FixedShift); + int fy1 = y1 - (rowIndex1 << FixedShift); + + // p/delta/mod/rem implement an integer DDA that advances x at row boundaries + // without per-row floating-point math. + int p = (FixedOne - fy0) * dx; + int delta = p / dy; + int cx = x0 + delta; + + this.RowDownR_V(rowIndex0, x0, fy0, cx, FixedOne); + + int row = rowIndex0 + 1; + if (row != rowIndex1) + { + int mod = (p % dy) - dy; + p = FixedOne * dx; + int lift = p / dy; + int rem = p % dy; + + for (; row != rowIndex1; row++) + { + delta = lift; + mod += rem; + if (mod >= 0) + { + mod -= dy; + delta++; + } + + int nx = cx + delta; + this.RowDownR_V(row, cx, 0, nx, FixedOne); + cx = nx; + } + } + + this.RowDownR_V(rowIndex1, cx, 0, x1, fy1); + } + + /// + /// Rasterizes an upward, left-to-right segment spanning multiple rows. + /// + private void LineUpR(int rowIndex0, int rowIndex1, int x0, int y0, int x1, int y1) + { + int dx = x1 - x0; + int dy = y0 - y1; + int fy0 = y0 - (rowIndex0 << FixedShift); + int fy1 = y1 - (rowIndex1 << FixedShift); + + // Upward version of the same integer DDA stepping as LineDownR. + int p = fy0 * dx; + int delta = p / dy; + int cx = x0 + delta; + + this.RowUpR_V(rowIndex0, x0, fy0, cx, 0); + + int row = rowIndex0 - 1; + if (row != rowIndex1) + { + int mod = (p % dy) - dy; + p = FixedOne * dx; + int lift = p / dy; + int rem = p % dy; + + for (; row != rowIndex1; row--) + { + delta = lift; + mod += rem; + if (mod >= 0) + { + mod -= dy; + delta++; + } + + int nx = cx + delta; + this.RowUpR_V(row, cx, FixedOne, nx, 0); + cx = nx; + } + } + + this.RowUpR_V(rowIndex1, cx, FixedOne, x1, fy1); + } + + /// + /// Rasterizes a downward, right-to-left segment spanning multiple rows. + /// + private void LineDownL(int rowIndex0, int rowIndex1, int x0, int y0, int x1, int y1) + { + int dx = x0 - x1; + int dy = y1 - y0; + int fy0 = y0 - (rowIndex0 << FixedShift); + int fy1 = y1 - (rowIndex1 << FixedShift); + + // Right-to-left variant of the integer DDA. + int p = (FixedOne - fy0) * dx; + int delta = p / dy; + int cx = x0 - delta; + + this.RowDownL_V(rowIndex0, x0, fy0, cx, FixedOne); + + int row = rowIndex0 + 1; + if (row != rowIndex1) + { + int mod = (p % dy) - dy; + p = FixedOne * dx; + int lift = p / dy; + int rem = p % dy; + + for (; row != rowIndex1; row++) + { + delta = lift; + mod += rem; + if (mod >= 0) + { + mod -= dy; + delta++; + } + + int nx = cx - delta; + this.RowDownL_V(row, cx, 0, nx, FixedOne); + cx = nx; + } + } + + this.RowDownL_V(rowIndex1, cx, 0, x1, fy1); + } + + /// + /// Rasterizes an upward, right-to-left segment spanning multiple rows. + /// + private void LineUpL(int rowIndex0, int rowIndex1, int x0, int y0, int x1, int y1) + { + int dx = x0 - x1; + int dy = y0 - y1; + int fy0 = y0 - (rowIndex0 << FixedShift); + int fy1 = y1 - (rowIndex1 << FixedShift); + + // Upward + right-to-left variant of the integer DDA. + int p = fy0 * dx; + int delta = p / dy; + int cx = x0 - delta; + + this.RowUpL_V(rowIndex0, x0, fy0, cx, 0); + + int row = rowIndex0 - 1; + if (row != rowIndex1) + { + int mod = (p % dy) - dy; + p = FixedOne * dx; + int lift = p / dy; + int rem = p % dy; + + for (; row != rowIndex1; row--) + { + delta = lift; + mod += rem; + if (mod >= 0) + { + mod -= dy; + delta++; + } + + int nx = cx - delta; + this.RowUpL_V(row, cx, FixedOne, nx, 0); + cx = nx; + } + } + + this.RowUpL_V(rowIndex1, cx, FixedOne, x1, fy1); + } + + /// + /// Dispatches a clipped edge to the correct directional fixed-point walker. + /// + private void RasterizeLine(int x0, int y0, int x1, int y1) + { + if (x0 == x1) + { + // Vertical edges need ownership adjustment to avoid double counting at cell seams. + int columnIndex = (x0 - FindAdjustment(x0)) >> FixedShift; + if (y0 < y1) + { + this.VerticalDown(columnIndex, y0, y1, x0); + } + else + { + this.VerticalUp(columnIndex, y0, y1, x0); + } + + return; + } + + if (y0 < y1) + { + // Downward edges use inclusive top/exclusive bottom row mapping. + int rowIndex0 = y0 >> FixedShift; + int rowIndex1 = (y1 - 1) >> FixedShift; + if (rowIndex0 == rowIndex1) + { + int rowBase = rowIndex0 << FixedShift; + int localY0 = y0 - rowBase; + int localY1 = y1 - rowBase; + if (x0 < x1) + { + this.RowDownR(rowIndex0, x0, localY0, x1, localY1); + } + else + { + this.RowDownL(rowIndex0, x0, localY0, x1, localY1); + } + } + else if (x0 < x1) + { + this.LineDownR(rowIndex0, rowIndex1, x0, y0, x1, y1); + } + else + { + this.LineDownL(rowIndex0, rowIndex1, x0, y0, x1, y1); + } + + return; + } + + // Upward edges mirror the mapping to preserve winding consistency. + int upRowIndex0 = (y0 - 1) >> FixedShift; + int upRowIndex1 = y1 >> FixedShift; + if (upRowIndex0 == upRowIndex1) + { + int rowBase = upRowIndex0 << FixedShift; + int localY0 = y0 - rowBase; + int localY1 = y1 - rowBase; + if (x0 < x1) + { + this.RowUpR(upRowIndex0, x0, localY0, x1, localY1); + } + else + { + this.RowUpL(upRowIndex0, x0, localY0, x1, localY1); + } + } + else if (x0 < x1) + { + this.LineUpR(upRowIndex0, upRowIndex1, x0, y0, x1, y1); + } + else + { + this.LineUpL(upRowIndex0, upRowIndex1, x0, y0, x1, y1); + } + } + } + + /// + /// Immutable scanner-local edge record (16 bytes). + /// + /// + /// All coordinates are stored as signed 24.8 fixed-point integers for predictable hot-path + /// access without per-read unpacking. Row bounds are computed inline from Y coordinates + /// where needed. + /// + internal readonly struct EdgeData + { + /// + /// Gets edge start X in scanner-local coordinates (24.8 fixed-point). + /// + public readonly int X0; + + /// + /// Gets edge start Y in scanner-local coordinates (24.8 fixed-point). + /// + public readonly int Y0; + + /// + /// Gets edge end X in scanner-local coordinates (24.8 fixed-point). + /// + public readonly int X1; + + /// + /// Gets edge end Y in scanner-local coordinates (24.8 fixed-point). + /// + public readonly int Y1; + + /// + /// Initializes a new instance of the struct. + /// + public EdgeData(int x0, int y0, int x1, int y1) + { + this.X0 = x0; + this.Y0 = y0; + this.X1 = x1; + this.Y1 = y1; + } + } + + /// + /// Stroke centerline edge descriptor used for per-band parallel stroke expansion. + /// + /// + /// + /// Each descriptor represents one centerline edge with associated join/cap metadata. + /// During rasterization, each descriptor is expanded into outline polygon edges that + /// are rasterized directly via . + /// + /// + /// The layout mirrors the GPU StrokeExpandComputeShader edge format: + /// + /// Side edge (flags=0): (X0,Y0)→(X1,Y1) is the centerline segment. + /// Join edge (): (X0,Y0) is the vertex, (X1,Y1) is the previous endpoint, (AdjX,AdjY) is the next endpoint. + /// Cap edge (/): (X0,Y0) is the cap vertex, (X1,Y1) is the adjacent endpoint. + /// + /// + /// All coordinates are in scanner-local float space (relative to interest top-left with sampling offset). + /// + internal readonly struct StrokeEdgeData + { + public readonly float X0; + public readonly float Y0; + public readonly float X1; + public readonly float Y1; + public readonly float AdjX; + public readonly float AdjY; + public readonly StrokeEdgeFlags Flags; + + public StrokeEdgeData(float x0, float y0, float x1, float y1, StrokeEdgeFlags flags, float adjX = 0, float adjY = 0) + { + this.X0 = x0; + this.Y0 = y0; + this.X1 = x1; + this.Y1 = y1; + this.Flags = flags; + this.AdjX = adjX; + this.AdjY = adjY; + } + } + + /// + /// Reusable per-worker scratch buffers used by tiled and sequential band rasterization. + /// + internal sealed class WorkerScratch : IDisposable + { + private readonly int wordsPerRow; + private readonly int coverStride; + private readonly int width; + private readonly int tileCapacity; + private readonly IMemoryOwner bitVectorsOwner; + private readonly IMemoryOwner coverAreaOwner; + private readonly IMemoryOwner startCoverOwner; + private readonly IMemoryOwner rowMinTouchedColumnOwner; + private readonly IMemoryOwner rowMaxTouchedColumnOwner; + private readonly IMemoryOwner rowHasBitsOwner; + private readonly IMemoryOwner rowTouchedOwner; + private readonly IMemoryOwner touchedRowsOwner; + private readonly IMemoryOwner scanlineOwner; + + private WorkerScratch( + int wordsPerRow, + int coverStride, + int width, + int tileCapacity, + IMemoryOwner bitVectorsOwner, + IMemoryOwner coverAreaOwner, + IMemoryOwner startCoverOwner, + IMemoryOwner rowMinTouchedColumnOwner, + IMemoryOwner rowMaxTouchedColumnOwner, + IMemoryOwner rowHasBitsOwner, + IMemoryOwner rowTouchedOwner, + IMemoryOwner touchedRowsOwner, + IMemoryOwner scanlineOwner) + { + this.wordsPerRow = wordsPerRow; + this.coverStride = coverStride; + this.width = width; + this.tileCapacity = tileCapacity; + this.bitVectorsOwner = bitVectorsOwner; + this.coverAreaOwner = coverAreaOwner; + this.startCoverOwner = startCoverOwner; + this.rowMinTouchedColumnOwner = rowMinTouchedColumnOwner; + this.rowMaxTouchedColumnOwner = rowMaxTouchedColumnOwner; + this.rowHasBitsOwner = rowHasBitsOwner; + this.rowTouchedOwner = rowTouchedOwner; + this.touchedRowsOwner = touchedRowsOwner; + this.scanlineOwner = scanlineOwner; + } + + /// + /// Gets reusable scanline scratch for this worker. + /// + public Span Scanline => this.scanlineOwner.Memory.Span; + + /// + /// Returns when this scratch has compatible dimensions and sufficient + /// capacity for the requested parameters, making it safe to reuse without reallocation. + /// + internal bool CanReuse(int requiredWordsPerRow, int requiredCoverStride, int requiredWidth, int minCapacity) + => this.wordsPerRow == requiredWordsPerRow + && this.coverStride == requiredCoverStride + && this.width == requiredWidth + && this.tileCapacity >= minCapacity; + + /// + /// Allocates worker-local scratch sized for the configured tile/band capacity. + /// + public static WorkerScratch Create(MemoryAllocator allocator, int wordsPerRow, int coverStride, int width, int tileCapacity) + { + int bitVectorCapacity = checked(wordsPerRow * tileCapacity); + int coverAreaCapacity = checked(coverStride * tileCapacity); + IMemoryOwner bitVectorsOwner = allocator.Allocate(bitVectorCapacity, AllocationOptions.Clean); + IMemoryOwner coverAreaOwner = allocator.Allocate(coverAreaCapacity); + IMemoryOwner startCoverOwner = allocator.Allocate(tileCapacity, AllocationOptions.Clean); + IMemoryOwner rowMinTouchedColumnOwner = allocator.Allocate(tileCapacity); + IMemoryOwner rowMaxTouchedColumnOwner = allocator.Allocate(tileCapacity); + IMemoryOwner rowHasBitsOwner = allocator.Allocate(tileCapacity, AllocationOptions.Clean); + IMemoryOwner rowTouchedOwner = allocator.Allocate(tileCapacity, AllocationOptions.Clean); + IMemoryOwner touchedRowsOwner = allocator.Allocate(tileCapacity); + IMemoryOwner scanlineOwner = allocator.Allocate(width); + + return new WorkerScratch( + wordsPerRow, + coverStride, + width, + tileCapacity, + bitVectorsOwner, + coverAreaOwner, + startCoverOwner, + rowMinTouchedColumnOwner, + rowMaxTouchedColumnOwner, + rowHasBitsOwner, + rowTouchedOwner, + touchedRowsOwner, + scanlineOwner); + } + + /// + /// Creates a context view over this scratch for the requested band height. + /// + public Context CreateContext(int bandHeight, IntersectionRule intersectionRule, RasterizationMode rasterizationMode, float antialiasThreshold) + { + if ((uint)bandHeight > (uint)this.tileCapacity) + { + ThrowBandHeightExceedsScratchCapacity(); + } + + int bitVectorCount = checked(this.wordsPerRow * bandHeight); + int coverAreaCount = checked(this.coverStride * bandHeight); + return new Context( + this.bitVectorsOwner.Memory.Span[..bitVectorCount], + this.coverAreaOwner.Memory.Span[..coverAreaCount], + this.startCoverOwner.Memory.Span[..bandHeight], + this.rowMinTouchedColumnOwner.Memory.Span[..bandHeight], + this.rowMaxTouchedColumnOwner.Memory.Span[..bandHeight], + this.rowHasBitsOwner.Memory.Span[..bandHeight], + this.rowTouchedOwner.Memory.Span[..bandHeight], + this.touchedRowsOwner.Memory.Span[..bandHeight], + this.width, + bandHeight, + this.wordsPerRow, + this.coverStride, + intersectionRule, + rasterizationMode, + antialiasThreshold); + } + + /// + /// Releases worker-local scratch buffers back to the allocator. + /// + public void Dispose() + { + this.bitVectorsOwner.Dispose(); + this.coverAreaOwner.Dispose(); + this.startCoverOwner.Dispose(); + this.rowMinTouchedColumnOwner.Dispose(); + this.rowMaxTouchedColumnOwner.Dispose(); + this.rowHasBitsOwner.Dispose(); + this.rowTouchedOwner.Dispose(); + this.touchedRowsOwner.Dispose(); + this.scanlineOwner.Dispose(); + } + } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/ICanvasFrame.cs b/src/ImageSharp.Drawing/Processing/Backends/ICanvasFrame.cs new file mode 100644 index 000000000..f3cb2e795 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/ICanvasFrame.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Per-frame destination for . +/// +/// The pixel format. +public interface ICanvasFrame + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the frame bounds in root target coordinates. + /// + public Rectangle Bounds { get; } + + /// + /// Attempts to get a CPU-accessible destination region. + /// + /// The CPU region when available. + /// when a CPU region is available. + public bool TryGetCpuRegion(out Buffer2DRegion region); + + /// + /// Attempts to get an opaque native destination surface. + /// + /// The native surface when available. + /// when a native surface is available. + public bool TryGetNativeSurface([NotNullWhen(true)] out NativeSurface? surface); +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/IDrawingBackend.cs b/src/ImageSharp.Drawing/Processing/Backends/IDrawingBackend.cs index 92f6f56e4..ce0a85340 100644 --- a/src/ImageSharp.Drawing/Processing/Backends/IDrawingBackend.cs +++ b/src/ImageSharp.Drawing/Processing/Backends/IDrawingBackend.cs @@ -1,62 +1,100 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Drawing.Processing.Backends; /// -/// Internal drawing backend abstraction used by processors. +/// Drawing backend abstraction used by processors. /// -/// -/// This boundary allows processor logic to stay stable while the implementation evolves -/// (for example: alternate CPU rasterizers or eventual non-CPU backends). -/// -internal interface IDrawingBackend +public interface IDrawingBackend { /// - /// Fills a path into the destination image using the given brush and drawing options. + /// Gets a value indicating whether this backend is available on the current system. + /// + public bool IsSupported => true; + + /// + /// Flushes queued composition operations for the target. /// - /// - /// This operation-level API keeps processors independent from scanline rasterization details, - /// allowing alternate backend implementations (for example GPU backends) to consume brush - /// and path data directly. - /// /// The pixel format. /// Active processing configuration. - /// Destination image frame. - /// The path to rasterize. - /// Brush used to shade covered pixels. - /// Graphics blending/composition options. - /// Rasterizer options. - /// Brush bounds used when creating the applicator. - /// Allocator for temporary data. - public void FillPath( + /// Destination frame. + /// Scene commands in submission order. + public void FlushCompositions( + Configuration configuration, + ICanvasFrame target, + CompositionScene compositionScene) + where TPixel : unmanaged, IPixel; + + /// + /// Attempts to read source pixels from the target into a caller-provided buffer. + /// + /// The pixel format. + /// The active processing configuration. + /// The target frame. + /// Source rectangle in target-local coordinates. + /// + /// The caller-allocated buffer to receive the pixel data. + /// Must be at least as large as (clamped to target bounds). + /// + /// when readback succeeds; otherwise . + public bool TryReadRegion( Configuration configuration, - ImageFrame source, - IPath path, - Brush brush, - in GraphicsOptions graphicsOptions, - in RasterizerOptions rasterizerOptions, - Rectangle brushBounds, - MemoryAllocator allocator) + ICanvasFrame target, + Rectangle sourceRectangle, + Buffer2D destination) where TPixel : unmanaged, IPixel; /// - /// Rasterizes path coverage into a floating-point destination map. + /// Composites a layer surface onto a destination frame using the specified graphics options. /// - /// - /// Coverage values are written in local destination coordinates where (0,0) maps to - /// the top-left of . - /// - /// The path to rasterize. - /// Rasterizer options. - /// Allocator for temporary data. - /// Destination coverage map. - public void RasterizeCoverage( - IPath path, - in RasterizerOptions rasterizerOptions, - MemoryAllocator allocator, - Buffer2D destination); + /// The pixel format. + /// Active processing configuration. + /// The layer frame to composite. + /// The destination frame to composite onto. + /// + /// The offset in the destination where the layer's top-left corner is placed. + /// + /// + /// Graphics options controlling blend mode, alpha composition, and opacity. + /// + public void ComposeLayer( + Configuration configuration, + ICanvasFrame source, + ICanvasFrame destination, + Point destinationOffset, + GraphicsOptions options) + where TPixel : unmanaged, IPixel; + + /// + /// Creates a layer frame for use during SaveLayer. + /// The returned frame is initialized to transparent and must be disposed by the caller. + /// CPU backends return a frame backed by an ; + /// GPU backends may return a native frame backed by a GPU texture. + /// + /// The pixel format. + /// Active processing configuration. + /// The current target frame that the layer will composite onto. + /// Layer width in pixels. + /// Layer height in pixels. + /// A new disposable layer frame. + public ICanvasFrame CreateLayerFrame( + Configuration configuration, + ICanvasFrame parentTarget, + int width, + int height) + where TPixel : unmanaged, IPixel; + + /// + /// Releases any backend resources cached against the specified target frame. + /// + /// The pixel format. + /// Active processing configuration. + /// The target frame whose resources should be released. + public void ReleaseFrameResources( + Configuration configuration, + ICanvasFrame target) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp.Drawing/Processing/Backends/MemoryCanvasFrame{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Backends/MemoryCanvasFrame{TPixel}.cs new file mode 100644 index 000000000..6105eca8e --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/MemoryCanvasFrame{TPixel}.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Canvas frame adapter over a . +/// +/// The pixel format. +public sealed class MemoryCanvasFrame : ICanvasFrame + where TPixel : unmanaged, IPixel +{ + private readonly Buffer2DRegion region; + + /// + /// Initializes a new instance of the class. + /// + /// The pixel buffer region backing this frame. + public MemoryCanvasFrame(Buffer2DRegion region) + { + Guard.NotNull(region.Buffer, nameof(region)); + this.region = region; + } + + /// + public Rectangle Bounds => this.region.Rectangle; + + /// + public bool TryGetCpuRegion(out Buffer2DRegion region) + { + region = this.region; + return true; + } + + /// + public bool TryGetNativeSurface([NotNullWhen(true)] out NativeSurface? surface) + { + surface = null; + return false; + } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/NativeCanvasFrame{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Backends/NativeCanvasFrame{TPixel}.cs new file mode 100644 index 000000000..b09d39b5a --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/NativeCanvasFrame{TPixel}.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Canvas frame adapter over a . +/// +/// The pixel format. +public sealed class NativeCanvasFrame : ICanvasFrame + where TPixel : unmanaged, IPixel +{ + private readonly NativeSurface surface; + + /// + /// Initializes a new instance of the class. + /// + /// The frame bounds. + /// The native surface backing this frame. + public NativeCanvasFrame(Rectangle bounds, NativeSurface surface) + { + Guard.NotNull(surface, nameof(surface)); + this.Bounds = bounds; + this.surface = surface; + } + + /// + public Rectangle Bounds { get; } + + /// + public bool TryGetCpuRegion(out Buffer2DRegion region) + { + region = default; + return false; + } + + /// + public bool TryGetNativeSurface([NotNullWhen(true)] out NativeSurface? surface) + { + surface = this.surface; + return true; + } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/NativeSurface.cs b/src/ImageSharp.Drawing/Processing/Backends/NativeSurface.cs new file mode 100644 index 000000000..5791ae03a --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/NativeSurface.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Opaque native destination with backend capability attachments. +/// +public sealed class NativeSurface +{ + private readonly ConcurrentDictionary capabilities = new(); + + /// + /// Initializes a new instance of the class. + /// + /// Pixel format information for the destination surface. + public NativeSurface(PixelTypeInfo pixelType) + => this.PixelType = pixelType; + + /// + /// Gets pixel format information for this destination surface. + /// + public PixelTypeInfo PixelType { get; } + + /// + /// Sets or replaces a capability object. + /// + /// Capability type. + /// Capability instance. + public void SetCapability(TCapability capability) + where TCapability : class + { + Guard.NotNull(capability, nameof(capability)); + this.capabilities[typeof(TCapability)] = capability; + } + + /// + /// Attempts to get a capability object by type. + /// + /// Capability type. + /// Capability instance when available. + /// when found. + public bool TryGetCapability([NotNullWhen(true)] out TCapability? capability) + where TCapability : class + { + if (this.capabilities.TryGetValue(typeof(TCapability), out object? value) && value is TCapability typed) + { + capability = typed; + return true; + } + + capability = null; + return false; + } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/PolygonScanning.MD b/src/ImageSharp.Drawing/Processing/Backends/PolygonScanning.MD new file mode 100644 index 000000000..16e6b5555 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/PolygonScanning.MD @@ -0,0 +1,304 @@ +# DefaultRasterizer (Fixed-Point, Tiled + Banded Fallback) + +This document describes the current `DefaultRasterizer` implementation in +`src/ImageSharp.Drawing/Shapes/Rasterization/DefaultRasterizer.cs`. + +The scanner is a fixed-point, area/cover rasterizer inspired by Blaze-style +scan conversion. + +https://github.com/aurimasg/blaze (MIT-Licensed) + +## Goals + +- Robustly rasterize arbitrary tessellated polygon rings (including self intersections). +- Support `EvenOdd` and `NonZero` fill rules. +- Keep temporary memory bounded for large targets. +- Emit coverage spans efficiently for blending. + +## High-Level Pipeline + +### Fill Path (`RasterizeRows`) + +``` +IPath + | + v +TessellatedMultipolygon.Create(...) + | + v +Choose execution mode: + | + +--> Parallel row-tiles (default rasterizer path) + | | + | +--> Build edge table once (global local-space edges) + | +--> Assign edges to tile rows + | +--> Rasterize each tile in parallel using worker-local scratch + | +--> Emit tile outputs in deterministic top-to-bottom order + | + +--> Sequential band loop (scanline baseline + fallback) + | + +--> Build edge table once (shared with parallel path) + +--> Assign edges to sequential bands + +--> Reuse worker scratch across bands + +--> Rasterize band-local edge subsets into cover/area accumulators + +--> Convert accumulators to coverage scanlines + +--> Invoke rasterizer callback per dirty row +``` + +### Stroke Path (`RasterizeStrokeRows`) + +Stroke rasterization fuses stroke expansion with coverage rasterization so that +each parallel band only expands the centerline edges that overlap it. This avoids +the cost of a serial full-path `GenerateOutline()` call and eliminates the +intermediate `IPath` allocation for the expanded outline. + +For dashed strokes, `SplitPathExtensions` splits the centerline into dash segments +on the CPU before passing the result through the same per-band stroke expansion +pipeline. + +``` +IPath (centerline) + | + +--> [if dashed] SplitPathExtensions.GenerateDashes(path, strokeWidth, pattern) + | + v +path.Flatten() -> List (preserving open/closed state) + | + v +BuildStrokeEdgeTable(contours) -> StrokeEdgeData[] + | For each contour: + | - Closed: N side edges + N join descriptors = 2N descriptors + | - Open: (N-1) side edges + (N-2) joins + 2 caps = 2N-1 descriptors + | Each descriptor carries StrokeEdgeFlags (None/Join/CapStart/CapEnd) + | + v +TryBuildBandSortedStrokeEdges(edges, expansion) + | Band-sort with Y expansion = halfWidth * max(miterLimit, 1) + | to ensure join/cap geometry reaches all overlapping bands + | + v +Choose execution mode: + | + +--> Parallel row-tiles + | | + | +--> Per tile: ExpandStrokeEdges -> EmitOutlineEdge -> RasterizeLine + | +--> EmitCoverageRows -> ordered emit via output buffer + | + +--> Sequential band loop + | + +--> Per band: ExpandStrokeEdges -> EmitOutlineEdge -> RasterizeLine + +--> EmitCoverageRows -> direct callback +``` + +#### Stroke Edge Expansion (`ExpandStrokeEdges`) + +Each `StrokeEdgeData` descriptor is expanded into outline edges based on its +`StrokeEdgeFlags`, mirroring the GPU `StrokeExpandComputeShader`: + +| Flag | Expansion | Outline edges | +|------|-----------|---------------| +| `None` (side) | Two edges offset by stroke normal | 2 | +| `Join` | Inner bevel + outer join (miter/round/bevel) | 2-N (round scales with width) | +| `CapStart`/`CapEnd` | Cap geometry (butt/square/round) | 1-N (round scales with width) | + +Outline edges are converted from float to 24.8 fixed-point, clipped to band +bounds, and fed directly to `RasterizeLine` — no intermediate edge buffer. + +## Coordinate System and Precision + +- Geometry is transformed to scanner-local coordinates: + - `xLocal = (x - interest.Left) + samplingOffsetX` + - `yLocal = (y - interest.Top) + samplingOffsetY` (global local-space edge table) + - Per tile/band pass uses `yLocal - currentBandTop` +- Scanner math uses signed 24.8 fixed point: + - `FixedShift = 8` + - `FixedOne = 256` +- Coverage is normalized to `[0..1]` with 256 steps: + - `CoverageStepCount = 256` + - `CoverageScale = 1 / 256` + +This means 1 fixed unit in Y equals 1/256 pixel row resolution. + +## Memory Model and Banded Scratch + +The scanner bounds scratch memory with a per-band budget: + +- `BandMemoryBudgetBytes = 64 MB` +- Rows per band are computed from per-row byte cost. + +Per-row temporary storage: + +``` +bitVectors: wordsPerRow * sizeof(nuint) +coverArea : (width * 2) * sizeof(int) +startCover: 1 * sizeof(int) +``` + +Scratch buffers are reused per band/tile worker: + +``` +bitVectors : [bandHeight][wordsPerRow] // bitset marks touched columns +coverArea : [bandHeight][width * 2] // per x: [deltaCover, deltaArea] +startCover : [bandHeight] // carry-in cover at x=0 +rowHasBits : [bandHeight] // fast "row touched" flag +scanline : [width] float // output coverage row +``` + +If width/height are too large for safe indexing math, rasterization throws +`ImageProcessingException`. + +Parallel mode additionally buffers per-tile output coverage before ordered emit. +This path is capped by `ParallelOutputPixelBudget` to avoid pathological output +buffer growth. + +## Edge Rasterization Stage + +For each tessellated ring edge `(p0 -> p1)` during edge-table build: + +1. Translate to local coordinates. +2. Reject non-finite coordinates. +3. Clip vertically to scanner bounds. +4. Record edge row range for tile assignment. + +During tile/band rasterization: + +1. Clip edge to current tile/band vertical bounds. +2. Convert endpoints to 24.8 fixed. +3. Skip horizontal edges (`fy0 == fy1`). +4. Route to directional line walkers (`LineDownR`, `LineUpL`, etc.). + +The walkers decompose edges into affected cells and call: + +- `Cell(...)` for general segments +- `CellVertical(...)` for vertical segments + +Both end up in `AddCell(row, column, deltaCover, deltaArea)`. + +`AddCell` updates: + +- `coverArea[row, column * 2 + 0] += deltaCover` +- `coverArea[row, column * 2 + 1] += deltaArea` +- bit in `bitVectors[row]` for `column` +- `rowHasBits[row] = 1` + +If `column < 0`, the contribution is folded into `startCover[row]` so coverage +to the left of the interest rectangle still influences pixels at `x >= 0`. + +## Scanline Emission Stage + +For each row in the current band: + +1. Skip quickly if `startCover[row] == 0` and `rowHasBits[row] == 0`. +2. Iterate set bits in the row bitset (`TrailingZeroCount` walk). +3. Reconstruct area/cover state at each touched `x`. +4. Convert signed accumulated area to coverage via fill rule. +5. Coalesce equal coverage into spans. +6. Fill `scanline[start..end]` for each non-zero span. +7. Invoke callback for dirty rows only. + +Core conversion: + +``` +area = coverArea[deltaArea] + (cover << 9) +``` + +`cover` is updated incrementally by `deltaCover`. + +## Fill Rule Handling + +### NonZero + +``` +absArea = abs(signedArea) +coverage = min(absArea, 256) / 256 +``` + +### EvenOdd + +``` +wrapped = absArea & 511 +if wrapped > 256: wrapped = 512 - wrapped +coverage = min(wrapped, 256) / 256 +``` + +This is done in `AreaToCoverage(int area)`. + +### Aliased Thresholding + +When `RasterizationMode` is `Aliased`, the continuous coverage value computed +above is snapped to binary using `AntialiasThreshold`: + +``` +if coverage >= antialiasThreshold: + coverage = 1.0 +else: + coverage = 0.0 +``` + +Lower threshold values (e.g. 0.1) preserve more edge pixels and thin features; +higher values (e.g. 0.9) produce a tighter, more conservative fill. The default +threshold is 0.5. + +## Why This Handles Self Intersections + +The scanner does not require geometric boolean normalization first. +Overlaps are resolved by accumulated area/cover integration and final fill-rule +mapping (`EvenOdd` or `NonZero`), so winding/parity behavior is decided at +rasterization time. + +## Fast Paths and Practical Optimizations + +- One tessellation build per rasterization call. +- Parallel path builds a single edge table and reuses it across tiles. +- Worker-local scratch reuse avoids per-tile scratch allocations. +- Sequential path reuses band buffers across the full Y range. +- `rowHasBits` avoids scanning all words in empty rows. +- Bitset iteration visits only touched columns. +- Span coalescing reduces per-pixel operations before blending. + +## Notes on Public Options + +- `RasterizerOptions.RasterizationMode` controls whether scanner output is: + - `Antialiased`: continuous coverage in `[0, 1]` + - `Aliased`: binary coverage (`0` or `1`), thresholded in the scanner using `AntialiasThreshold` +- `RasterizerOptions.AntialiasThreshold` (0–1, default 0.5): the coverage cutoff + used in `Aliased` mode. Pixels with coverage at or above this value become fully + opaque; pixels below are discarded. Ignored in `Antialiased` mode. +- `RasterizerSamplingOrigin` affects both X and Y sample alignment (`PixelBoundary` vs `PixelCenter`). + +## Data Flow Diagram (Row-Level) + +``` + per-edge writes + | + v + +----------------------+ + | coverArea[row][x,*] | deltaCover + deltaArea + +----------------------+ + | + +--> bitVectors[row] set bit x + | + +--> rowHasBits[row] = 1 + | + +--> startCover[row] (for x < 0 contributions) + +Then during emit: + +bitVectors[row] -> touched x list -> accumulate cover/area -> coverage spans + | + v + scanline[width] + | + v + Rasterizer callback +``` + +## Failure Modes and Diagnostics + +- Exception: interest too large for bounded scratch/output buffers or indexing. +- Symptoms like missing fill are usually from invalid input geometry (NaN/Inf) or + ring construction upstream; scanner explicitly skips non-finite edges. +- Performance hotspots are typically in: + - edge walking (`RasterizeLine` family), + - fill-rule conversion (`EmitRowCoverage`), + - downstream blending/compositing callbacks. diff --git a/src/ImageSharp.Drawing/Processing/Backends/PreparedCompositionCommand.cs b/src/ImageSharp.Drawing/Processing/Backends/PreparedCompositionCommand.cs new file mode 100644 index 000000000..366782163 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/PreparedCompositionCommand.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// One normalized composition command that applies a brush to the active coverage map. +/// +public struct PreparedCompositionCommand +{ + /// + /// Initializes a new instance of the struct. + /// + /// The destination region in target-local coordinates. + /// The source offset into the pre-rasterized coverage map. + /// The brush used during composition. + /// Brush bounds used for applicator creation. + /// Graphics options used during composition. + /// The absolute destination offset from the original composition command. + public PreparedCompositionCommand( + Rectangle destinationRegion, + Point sourceOffset, + Brush brush, + Rectangle brushBounds, + GraphicsOptions graphicsOptions, + Point destinationOffset) + { + this.DestinationRegion = destinationRegion; + this.SourceOffset = sourceOffset; + this.Brush = brush; + this.BrushBounds = brushBounds; + this.GraphicsOptions = graphicsOptions; + this.DestinationOffset = destinationOffset; + } + + /// + /// Gets or sets the destination region in target-local coordinates. + /// + public Rectangle DestinationRegion { get; set; } + + /// + /// Gets or sets the source offset into the pre-rasterized coverage map. + /// + public Point SourceOffset { get; set; } + + /// + /// Gets the brush used during composition. + /// + public Brush Brush { get; } + + /// + /// Gets brush bounds used for applicator creation. + /// + public Rectangle BrushBounds { get; } + + /// + /// Gets graphics options used during composition. + /// + public GraphicsOptions GraphicsOptions { get; } + + /// + /// Gets the absolute destination offset from the original composition command. + /// + public Point DestinationOffset { get; } +} diff --git a/src/ImageSharp.Drawing/Processing/Backends/RasterizerCoverageRowHandler.cs b/src/ImageSharp.Drawing/Processing/Backends/RasterizerCoverageRowHandler.cs new file mode 100644 index 000000000..405c2cd76 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/RasterizerCoverageRowHandler.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Delegate invoked for each emitted non-zero coverage span. +/// +/// The destination y coordinate. +/// The first x coordinate represented by . +/// Non-zero coverage values starting at . +internal delegate void RasterizerCoverageRowHandler(int y, int startX, Span coverage); diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/RasterizerOptions.cs b/src/ImageSharp.Drawing/Processing/Backends/RasterizerOptions.cs similarity index 75% rename from src/ImageSharp.Drawing/Shapes/Rasterization/RasterizerOptions.cs rename to src/ImageSharp.Drawing/Processing/Backends/RasterizerOptions.cs index 66a8cfbb7..9f1639047 100644 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/RasterizerOptions.cs +++ b/src/ImageSharp.Drawing/Processing/Backends/RasterizerOptions.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Drawing.Shapes.Rasterization; +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; /// /// Describes whether rasterizers should emit continuous coverage or binary aliased coverage. /// -internal enum RasterizationMode +public enum RasterizationMode { /// /// Emit continuous coverage in the range [0, 1]. @@ -22,7 +22,7 @@ internal enum RasterizationMode /// /// Describes where sample coverage is aligned relative to destination pixels. /// -internal enum RasterizerSamplingOrigin +public enum RasterizerSamplingOrigin { /// /// Samples are aligned to pixel boundaries. @@ -38,7 +38,7 @@ internal enum RasterizerSamplingOrigin /// /// Immutable options used by rasterizers when scan-converting vector geometry. /// -internal readonly struct RasterizerOptions +public readonly struct RasterizerOptions { /// /// Initializes a new instance of the struct. @@ -47,16 +47,19 @@ internal readonly struct RasterizerOptions /// Polygon intersection rule. /// Rasterization coverage mode. /// Sampling origin alignment. + /// Coverage threshold for aliased mode (0–1). public RasterizerOptions( Rectangle interest, IntersectionRule intersectionRule, - RasterizationMode rasterizationMode = RasterizationMode.Antialiased, - RasterizerSamplingOrigin samplingOrigin = RasterizerSamplingOrigin.PixelBoundary) + RasterizationMode rasterizationMode, + RasterizerSamplingOrigin samplingOrigin, + float antialiasThreshold) { this.Interest = interest; this.IntersectionRule = intersectionRule; this.RasterizationMode = rasterizationMode; this.SamplingOrigin = samplingOrigin; + this.AntialiasThreshold = antialiasThreshold; } /// @@ -79,11 +82,17 @@ public RasterizerOptions( /// public RasterizerSamplingOrigin SamplingOrigin { get; } + /// + /// Gets the coverage threshold used when is . + /// Pixels with coverage above this value are rendered as fully opaque; pixels below are discarded. + /// + public float AntialiasThreshold { get; } + /// /// Creates a copy of the current options with a different interest rectangle. /// /// The replacement interest rectangle. /// A new value. public RasterizerOptions WithInterest(Rectangle interest) - => new(interest, this.IntersectionRule, this.RasterizationMode, this.SamplingOrigin); + => new(interest, this.IntersectionRule, this.RasterizationMode, this.SamplingOrigin, this.AntialiasThreshold); } diff --git a/src/ImageSharp.Drawing/Processing/Backends/StrokeEdgeFlags.cs b/src/ImageSharp.Drawing/Processing/Backends/StrokeEdgeFlags.cs new file mode 100644 index 000000000..27156fde8 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/Backends/StrokeEdgeFlags.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing.Backends; + +/// +/// Bit flags identifying the type of a stroke edge descriptor. +/// Values match the WGSL shader constants in StrokeExpandComputeShader. +/// +[Flags] +public enum StrokeEdgeFlags +{ + /// + /// Side edge: (X0,Y0)→(X1,Y1) is a centerline segment. + /// + None = 0, + + /// + /// Join at a contour vertex. + /// (X0,Y0) is the vertex, (X1,Y1) is the previous endpoint, + /// (AdjX,AdjY) is the next endpoint. + /// + Join = 32, + + /// + /// Start cap on an open contour. + /// (X0,Y0) is the cap vertex, (X1,Y1) is the adjacent endpoint. + /// + CapStart = 64, + + /// + /// End cap on an open contour. + /// (X0,Y0) is the cap vertex, (X1,Y1) is the adjacent endpoint. + /// + CapEnd = 128, +} diff --git a/src/ImageSharp.Drawing/Processing/Brush.cs b/src/ImageSharp.Drawing/Processing/Brush.cs index 6b13530f5..73776b558 100644 --- a/src/ImageSharp.Drawing/Processing/Brush.cs +++ b/src/ImageSharp.Drawing/Processing/Brush.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Drawing.Processing; /// @@ -18,7 +21,7 @@ public abstract class Brush : IEquatable /// The pixel type. /// The configuration instance to use when performing operations. /// The graphic options. - /// The source image. + /// The destination pixel region. /// The region the brush will be applied to. /// /// The for this brush. @@ -30,10 +33,17 @@ public abstract class Brush : IEquatable public abstract BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) where TPixel : unmanaged, IPixel; + /// + /// Returns a new brush with its defining geometry transformed by the given matrix. + /// + /// The transformation matrix to apply. + /// A transformed brush, or this if the brush has no spatial parameters. + public virtual Brush Transform(Matrix4x4 matrix) => this; + /// public abstract bool Equals(Brush? other); diff --git a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs index 99b16023e..54c7b6a5b 100644 --- a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Drawing.Processing; /// @@ -16,11 +18,14 @@ public abstract class BrushApplicator : IDisposable /// /// The configuration instance to use when performing operations. /// The graphics options. - /// The target image frame. - protected BrushApplicator(Configuration configuration, GraphicsOptions options, ImageFrame target) + /// The destination pixel region. + protected BrushApplicator( + Configuration configuration, + GraphicsOptions options, + Buffer2DRegion targetRegion) { this.Configuration = configuration; - this.Target = target; + this.TargetRegion = targetRegion; this.Options = options; this.Blender = PixelOperations.Instance.GetPixelBlender(options); } @@ -36,9 +41,9 @@ protected BrushApplicator(Configuration configuration, GraphicsOptions options, internal PixelBlender Blender { get; } /// - /// Gets the target image frame. + /// Gets the destination region. /// - protected ImageFrame Target { get; } + protected Buffer2DRegion TargetRegion { get; } /// /// Gets the graphics options diff --git a/src/ImageSharp.Drawing/Processing/DRAWING_CANVAS.md b/src/ImageSharp.Drawing/Processing/DRAWING_CANVAS.md new file mode 100644 index 000000000..de77ba1d6 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DRAWING_CANVAS.md @@ -0,0 +1,295 @@ +# DrawingCanvas + +This document describes the architecture, state management, and object lifecycle of `DrawingCanvas`. + +## Overview + +`DrawingCanvas` is the high-level drawing API. It manages a state stack, command batching, layer compositing, and delegates rasterization to an `IDrawingBackend`. It implements a deferred command model—draw calls queue `CompositionCommand` objects in a batcher, which are flushed to the backend on `Flush()` or `Dispose()`. + +## Class Structure + +```text +DrawingCanvas : IDrawingCanvas, IDisposable + Fields: + configuration : Configuration + backend : IDrawingBackend + targetFrame : ICanvasFrame (root frame, immutable) + batcher : DrawingCanvasBatcher (reassigned on SaveLayer/Restore) + savedStates : Stack (min depth 1) + layerDataStack : Stack> (one per active SaveLayer) + pendingImageResources : List> (temp images awaiting flush) + isDisposed : bool +``` + +## State Management + +### DrawingCanvasState (Immutable Snapshot) + +```text +DrawingCanvasState + Options : DrawingOptions (by reference, not deep-cloned) + ClipPaths : IReadOnlyList + IsLayer : bool (init-only, default false) + LayerOptions : GraphicsOptions? (init-only, set for layers) + LayerBounds : Rectangle? (init-only, set for layers) +``` + +### Save / Restore + +```text +Save() + -> push new DrawingCanvasState(current.Options, current.ClipPaths) + -> IsLayer = false (prevents spurious layer compositing on Restore) + -> return SaveCount + +Save(options, clipPaths) + -> Save(), then replace top state with new options/clips + -> return SaveCount + +Restore() + -> if SaveCount <= 1: no-op + -> pop top state + -> if state.IsLayer: CompositeAndPopLayer(state) + +RestoreTo(saveCount) + -> pop states until SaveCount == target + -> each layer state triggers CompositeAndPopLayer +``` + +`ResolveState()` returns `savedStates.Peek()`—every drawing operation reads the active state from here. + +## SaveLayer Lifecycle + +### Purpose + +SaveLayer enables group-level effects: rendering multiple draw commands to an isolated temporary surface, then compositing the result back with opacity, blend mode, or restricted bounds. This is the mechanism behind SVG `` and group blend modes. + +### Push Phase + +```text +SaveLayer(layerOptions, bounds) + 1. Flush() pending commands to current target + 2. Clamp bounds to canvas (min 1x1) + 3. Allocate transparent Image(width, height) + 4. Wrap in MemoryCanvasFrame + 5. Save current batcher in LayerData: + LayerData(parentBatcher, layerImage, layerFrame, layerBounds) + 6. Push LayerData onto layerDataStack + 7. Create new batcher targeting layer frame + 8. Push layer state onto savedStates: + DrawingCanvasState { IsLayer=true, LayerOptions, LayerBounds } + 9. Return SaveCount +``` + +### Draw Phase + +All commands between SaveLayer() and Restore() target the layer batcher, which writes to the temporary layer image. Clip paths, transforms, and drawing options apply normally within the layer's local coordinate space. + +### Pop Phase (Restore) + +```text +CompositeAndPopLayer(layerState) + 1. Flush() pending commands to layer surface + 2. Pop LayerData from layerDataStack + 3. Restore parent batcher: this.batcher = layerData.ParentBatcher + 4. Get destination from parent batcher's target frame + 5. backend.ComposeLayer(source=layerFrame, destination, bounds.Location, layerOptions) + 6. layerData.Dispose() (releases temporary image) +``` + +### Nesting + +Layers nest naturally via the `layerDataStack`. When compositing a nested layer, the parent batcher still targets the intermediate layer (not root). Each Restore pops one layer and composites it to its immediate parent. + +### Object Lifecycle Diagram + +```text +canvas.SaveLayer(options, bounds) + ├─ Image layerImage ──────────────┐ (allocated) + ├─ MemoryCanvasFrame layerFrame ──────────┤ + ├─ LayerData ────────────────────┤ + │ ├─ .ParentBatcher = old batcher │ + │ ├─ .LayerImage = layerImage │ + │ ├─ .LayerFrame = layerFrame │ + │ └─ .LayerBounds = clampedBounds │ + └─ new batcher → targets layerFrame │ + │ + ... draw commands → layer batcher ... │ + │ +canvas.Restore() │ + ├─ flush layer batcher │ + ├─ pop LayerData │ + ├─ restore parent batcher │ + ├─ ComposeLayer(layerFrame → parent) │ + └─ layerData.Dispose() ─────────────────┘ (freed) +``` + +## The Batcher + +`DrawingCanvasBatcher` queues `CompositionCommand` objects and flushes them to the backend. + +```text +DrawingCanvasBatcher + TargetFrame : ICanvasFrame (destination for this batcher) + AddComposition(command) (append to command list) + FlushCompositions() (create scene, call backend, clear list) +``` + +**Flush behavior:** +1. If no commands, return (no-op). +2. Create `CompositionScene` wrapping the command list. +3. Call `backend.FlushCompositions(configuration, targetFrame, scene)`. +4. Clear commands in `finally` block (always, even on failure). + +The canvas holds exactly one batcher at a time. SaveLayer swaps it for a new one targeting the layer frame; Restore swaps it back to the parent. + +## Drawing Operations + +### Fill + +```text +Fill(brush, path) + 1. ResolveState() → options, clipPaths + 2. path.AsClosedPath() + 3. Apply transform: path.Transform(options.Transform), brush.Transform(options.Transform) + 4. Apply clips: path.Clip(shapeOptions, clipPaths) + 5. PrepareCompositionCore(path, brush, options, PixelBoundary) + → batcher.AddComposition(CompositionCommand.Create(...)) +``` + +### Stroke (Draw) + +```text +Draw(pen, path) + 1. ResolveState() → options, clipPaths + 2. Apply transform to path + 3. Force NonZero winding rule (strokes self-overlap) + 4. If clip paths present: + → expand stroke to outline on CPU, then clip, then fill + 5. If no clip paths: + → PrepareStrokeCompositionCore(path, brush, strokeWidth, strokeOptions, ...) + → defers stroke expansion to backend + Uses PixelCenter sampling origin +``` + +### Clear + +Executes a fill with modified `GraphicsOptions` (Src composition, no blending) inside a temporary state scope via `ExecuteWithTemporaryState()`. + +### DrawImage + +Three-phase pipeline: +1. **Source preparation:** Crop/scale source image to destination dimensions. +2. **Transform application:** Apply canvas transform via `image.Transform()` with composed matrix. +3. **Deferred execution:** Transfer temp image to `pendingImageResources`, create `ImageBrush`, fill destination path. + +### DrawText + +Renders text to glyph operations via `RichTextGlyphRenderer`, sorts by render pass (for color font layers), submits commands in sorted order. + +### Process + +Flushes current commands, reads back pixels via `backend.TryReadRegion()`, runs an `Action` on the readback, then fills the result back to the canvas via `ImageBrush`. + +## Frame Abstraction + +```text +ICanvasFrame + Bounds : Rectangle + TryGetCpuRegion(out region) : bool + TryGetNativeSurface(out surface) : bool +``` + +| Implementation | CPU Region | Native Surface | Usage | +|---|---|---|---| +| `MemoryCanvasFrame` | Yes | No | CPU image buffers, layers | +| `NativeCanvasFrame` | No | Yes | GPU-backed surfaces | +| `CanvasRegionFrame` | Delegates | Delegates | Sub-region of parent frame | + +`CanvasRegionFrame` wraps a parent frame with a clipped rectangle, used by `CreateRegion()`. + +## Transform Handling + +Transforms are stored in `DrawingOptions.Transform` as `Matrix4x4` (default: Identity). Applied per-operation, not cumulatively. + +| Target | Method | +|---|---| +| Paths | `path.Transform(matrix)` | +| Brushes | `brush.Transform(matrix)` | +| Images | `image.Transform()` with composed source→destination→canvas matrix | + +## Clipping + +Clip paths are stored in `DrawingCanvasState.ClipPaths`. Applied during command preparation: + +```csharp +effectivePath = subjectPath.Clip(shapeOptions, clipPaths); +``` + +For strokes with clip paths, the stroke is expanded to an outline first, then clipped, then filled—this prevents clip artifacts at stroke edges. + +## CreateRegion + +```text +CreateRegion(region) + -> clamp to canvas bounds + -> wrap frame in CanvasRegionFrame + -> create child canvas: + - shares backend and batcher with parent + - snapshots current state + - local origin is (0,0) within clipped region +``` + +## Disposal + +```text +Dispose() + Phase 1: Pop all active layers + -> for each layer in layerDataStack (LIFO): + -> Flush() to layer surface + -> restore parent batcher + -> ComposeLayer(layer → parent) with default GraphicsOptions + -> layerData.Dispose() + + Phase 2: Final flush + -> batcher.FlushCompositions() + + Phase 3: Cleanup (in finally) + -> DisposePendingImageResources() + -> isDisposed = true +``` + +Active layers that were never explicitly Restored are composited with default `GraphicsOptions` during disposal. This ensures no resource leaks, though the compositing result may differ from explicit Restore with custom options. + +## IDrawingBackend Interface + +```text +IDrawingBackend + IsSupported → bool (default true) + FlushCompositions (configuration, target, scene) + ComposeLayer (configuration, source, destination, offset, options) + TryReadRegion (configuration, target, rect, out image) → bool + ReleaseFrameResources (configuration, target) +``` + +`DefaultDrawingBackend` (singleton) handles all operations on CPU. `WebGPUDrawingBackend` accelerates FlushCompositions and ComposeLayer on GPU with CPU fallback. + +## Command Flow Summary + +```text +canvas.Fill(brush, path) + → transform path + brush + → clip path + → CompositionCommand.Create(path, brush, options) + → batcher.AddComposition(command) + → ... more draw calls ... + +canvas.Flush() or canvas.Dispose() + → batcher.FlushCompositions() + → CompositionScene(commands) + → backend.FlushCompositions(config, frame, scene) + → CompositionScenePlanner.CreatePreparedBatches() + → for each batch: rasterize + apply brushes + composite + → commands.Clear() + → DisposePendingImageResources() +``` diff --git a/src/ImageSharp.Drawing/Processing/DrawingCanvas.PreFlattenedPath.cs b/src/ImageSharp.Drawing/Processing/DrawingCanvas.PreFlattenedPath.cs new file mode 100644 index 000000000..cab98d643 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawingCanvas.PreFlattenedPath.cs @@ -0,0 +1,234 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Drawing.Processing; + +/// +/// Contains private pre-flattened path types used by the canvas to avoid redundant curve subdivision. +/// +public sealed partial class DrawingCanvas +{ + /// + /// A lightweight wrapper around pre-flattened points. + /// returns this directly, avoiding redundant curve subdivision. + /// Points are mutated in place on ; no buffers are copied. + /// + private sealed class FlattenedPath : IPath, ISimplePath + { + private readonly PointF[] points; + private readonly bool isClosed; + private RectangleF bounds; + + public FlattenedPath(PointF[] points, bool isClosed, RectangleF bounds) + { + this.points = points; + this.isClosed = isClosed; + this.bounds = bounds; + } + + /// + public RectangleF Bounds => this.bounds; + + /// + public PathTypes PathType => this.isClosed ? PathTypes.Closed : PathTypes.Open; + + /// + bool ISimplePath.IsClosed => this.isClosed; + + /// + ReadOnlyMemory ISimplePath.Points => this.points; + + /// + public IEnumerable Flatten() + { + yield return this; + } + + /// + /// Transforms all points in place and updates the bounds. + /// This mutates the current instance — the point buffer is not copied. + /// + /// The transform matrix. + /// This instance, with points and bounds updated. + public IPath Transform(Matrix4x4 matrix) + { + if (matrix.IsIdentity) + { + return this; + } + + float minX = float.MaxValue, minY = float.MaxValue; + float maxX = float.MinValue, maxY = float.MinValue; + + for (int i = 0; i < this.points.Length; i++) + { + ref PointF p = ref this.points[i]; + p = PointF.Transform(p, matrix); + + if (p.X < minX) + { + minX = p.X; + } + + if (p.Y < minY) + { + minY = p.Y; + } + + if (p.X > maxX) + { + maxX = p.X; + } + + if (p.Y > maxY) + { + maxY = p.Y; + } + } + + this.bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY); + return this; + } + + /// + public IPath AsClosedPath() + { + if (this.isClosed) + { + return this; + } + + PointF[] closedPoints = new PointF[this.points.Length + 1]; + for (int i = 0; i < this.points.Length; i++) + { + closedPoints[i] = this.points[i]; + } + + closedPoints[^1] = this.points[0]; + return new FlattenedPath(closedPoints, true, this.bounds); + } + } + + /// + /// A lightweight wrapper around multiple pre-flattened sub-paths. + /// yields each sub-path directly, avoiding redundant curve subdivision. + /// Sub-path points are mutated in place on ; no buffers are copied. + /// + private sealed class FlattenedCompositePath : IPath + { + private readonly FlattenedPath[] subPaths; + private RectangleF bounds; + private PathTypes? pathType; + + public FlattenedCompositePath(FlattenedPath[] subPaths, RectangleF bounds) + { + this.subPaths = subPaths; + this.bounds = bounds; + } + + /// + public RectangleF Bounds => this.bounds; + + /// + public PathTypes PathType + { + get + { + if (this.pathType.HasValue) + { + return this.pathType.Value; + } + + bool hasOpen = false; + bool hasClosed = false; + foreach (FlattenedPath sp in this.subPaths) + { + if (sp.PathType == PathTypes.Open) + { + hasOpen = true; + } + else + { + hasClosed = true; + } + + if (hasOpen && hasClosed) + { + return PathTypes.Mixed; + } + } + + this.pathType = hasClosed ? PathTypes.Closed : PathTypes.Open; + return this.pathType.Value; + } + } + + /// + public IEnumerable Flatten() => this.subPaths; + + /// + /// Transforms all sub-path points in place and updates the bounds. + /// This mutates the current instance — no buffers are copied. + /// + /// The transform matrix. + /// This instance, with all sub-paths and bounds updated. + public IPath Transform(Matrix4x4 matrix) + { + if (matrix.IsIdentity) + { + return this; + } + + float minX = float.MaxValue, minY = float.MaxValue; + float maxX = float.MinValue, maxY = float.MinValue; + + for (int i = 0; i < this.subPaths.Length; i++) + { + _ = this.subPaths[i].Transform(matrix); + RectangleF spBounds = this.subPaths[i].Bounds; + + if (spBounds.Left < minX) + { + minX = spBounds.Left; + } + + if (spBounds.Top < minY) + { + minY = spBounds.Top; + } + + if (spBounds.Right > maxX) + { + maxX = spBounds.Right; + } + + if (spBounds.Bottom > maxY) + { + maxY = spBounds.Bottom; + } + } + + this.bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY); + return this; + } + + /// + public IPath AsClosedPath() + { + if (this.PathType == PathTypes.Closed) + { + return this; + } + + FlattenedPath[] closed = new FlattenedPath[this.subPaths.Length]; + for (int i = 0; i < this.subPaths.Length; i++) + { + closed[i] = (FlattenedPath)this.subPaths[i].AsClosedPath(); + } + + return new FlattenedCompositePath(closed, this.bounds); + } + } +} diff --git a/src/ImageSharp.Drawing/Processing/DrawingCanvasBatcher{TPixel}.cs b/src/ImageSharp.Drawing/Processing/DrawingCanvasBatcher{TPixel}.cs new file mode 100644 index 000000000..12cc27ab6 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawingCanvasBatcher{TPixel}.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing.Backends; + +namespace SixLabors.ImageSharp.Drawing.Processing; + +/// +/// Queues normalized composition commands emitted by +/// and flushes them to in deterministic draw order. +/// +/// +/// The batcher owns command buffering and normalization only; it does not rasterize or composite. +/// During flush it emits a so each backend can plan execution +/// (for example: CPU batching or GPU tiling) without changing the canvas call surface. +/// +internal sealed class DrawingCanvasBatcher + where TPixel : unmanaged, IPixel +{ + private readonly Configuration configuration; + private readonly IDrawingBackend backend; + private readonly List commands = []; + + internal DrawingCanvasBatcher( + Configuration configuration, + IDrawingBackend backend, + ICanvasFrame targetFrame) + { + this.configuration = configuration; + this.backend = backend; + this.TargetFrame = targetFrame; + } + + /// + /// Gets the target frame that this batcher flushes to. + /// + public ICanvasFrame TargetFrame { get; } + + /// + /// Appends one normalized composition command to the pending queue. + /// + /// The command to queue. + public void AddComposition(in CompositionCommand composition) + => this.commands.Add(composition); + + /// + /// Flushes queued commands to the backend as one scene packet, preserving submission order. + /// + /// + /// Backends are responsible for planning execution (for example: grouping by coverage, caching, + /// or GPU binning). The batcher only records scene commands and forwards them on flush. + /// + public void FlushCompositions() + { + if (this.commands.Count == 0) + { + return; + } + + try + { + CompositionScene scene = new(this.commands); + this.backend.FlushCompositions(this.configuration, this.TargetFrame, scene); + } + finally + { + // Always clear the queue, even if backend flush throws. + this.commands.Clear(); + } + } +} diff --git a/src/ImageSharp.Drawing/Processing/DrawingCanvasState.cs b/src/ImageSharp.Drawing/Processing/DrawingCanvasState.cs new file mode 100644 index 000000000..374a2958b --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawingCanvasState.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing.Backends; + +namespace SixLabors.ImageSharp.Drawing.Processing; + +/// +/// Immutable drawing state snapshot used by . +/// +internal sealed class DrawingCanvasState +{ + /// + /// Initializes a new instance of the class. + /// + /// Drawing options for this state. + /// Clip paths for this state. + public DrawingCanvasState(DrawingOptions options, IReadOnlyList clipPaths) + { + this.Options = options; + this.ClipPaths = clipPaths; + } + + /// + /// Gets drawing options associated with this state. + /// + /// + /// This is the original reference supplied to the state. + /// It is not deep-cloned. + /// + public DrawingOptions Options { get; } + + /// + /// Gets clip paths associated with this state. + /// + public IReadOnlyList ClipPaths { get; } + + /// + /// Gets a value indicating whether this state represents a compositing layer. + /// + public bool IsLayer { get; init; } + + /// + /// Gets the graphics options used to composite this layer on restore. + /// Only set when is . + /// + public GraphicsOptions? LayerOptions { get; init; } + + /// + /// Gets the local bounds of this layer relative to the parent canvas. + /// Only set when is . + /// + public Rectangle? LayerBounds { get; init; } +} + +/// +/// Typed layer data that holds the parent batcher and temporary layer buffer. +/// +/// The pixel format. +internal sealed class LayerData + where TPixel : unmanaged, IPixel +{ + /// + /// Initializes a new instance of the class. + /// + /// The parent batcher to restore on layer pop. + /// The canvas frame wrapping the layer buffer. + /// The local bounds of this layer relative to the parent. + public LayerData( + DrawingCanvasBatcher parentBatcher, + ICanvasFrame layerFrame, + Rectangle layerBounds) + { + this.ParentBatcher = parentBatcher; + this.LayerFrame = layerFrame; + this.LayerBounds = layerBounds; + } + + /// + /// Gets the batcher that was active before this layer was pushed. + /// + public DrawingCanvasBatcher ParentBatcher { get; } + + /// + /// Gets the canvas frame wrapping the layer buffer. + /// + public ICanvasFrame LayerFrame { get; } + + /// + /// Gets the local bounds of this layer relative to the parent canvas. + /// + public Rectangle LayerBounds { get; } +} diff --git a/src/ImageSharp.Drawing/Processing/DrawingCanvas{TPixel}.cs b/src/ImageSharp.Drawing/Processing/DrawingCanvas{TPixel}.cs new file mode 100644 index 000000000..c3fb6f673 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawingCanvas{TPixel}.cs @@ -0,0 +1,1683 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +#pragma warning disable CA1000 // Do not declare static members on generic types + +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using SixLabors.Fonts; +using SixLabors.Fonts.Rendering; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Drawing.Processing.Backends; +using SixLabors.ImageSharp.Drawing.Processing.Processors.Text; +using SixLabors.ImageSharp.Drawing.Text; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Drawing.Processing; + +/// +/// A drawing canvas over a frame target. +/// +/// The pixel format. +public sealed partial class DrawingCanvas : IDrawingCanvas + where TPixel : unmanaged, IPixel +{ + /// + /// Processing configuration used by operations executed through this canvas. + /// + private readonly Configuration configuration; + + /// + /// Backend responsible for rasterizing and composing draw commands. + /// + private readonly IDrawingBackend backend; + + /// + /// Destination frame receiving rendered output. + /// + private readonly ICanvasFrame targetFrame; + + /// + /// Command batcher used to defer and submit composition commands. + /// Reassigned when a layer is pushed or popped via . + /// + private DrawingCanvasBatcher batcher; + + /// + /// Active layer data stack. Each entry corresponds to a + /// call on the saved-states stack and holds the parent batcher and temporary layer buffer. + /// + private readonly Stack> layerDataStack = new(); + + /// + /// Temporary image resources that must stay alive until queued commands are flushed. + /// + private readonly List> pendingImageResources = []; + + /// + /// Tracks whether this instance has already been disposed. + /// + private bool isDisposed; + + /// + /// Stack of saved drawing states for Save/Restore operations. + /// + private readonly Stack savedStates = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The active processing configuration. + /// The destination target region. + /// Initial drawing options for this canvas instance. + /// Initial clip paths for this canvas instance. + public DrawingCanvas( + Configuration configuration, + Buffer2DRegion targetRegion, + DrawingOptions options, + params IPath[] clipPaths) + : this(configuration, new MemoryCanvasFrame(targetRegion), options, clipPaths) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The active processing configuration. + /// The destination frame. + /// Initial drawing options for this canvas instance. + /// Initial clip paths for this canvas instance. + public DrawingCanvas( + Configuration configuration, + ICanvasFrame targetFrame, + DrawingOptions options, + params IPath[] clipPaths) + : this(configuration, configuration.GetDrawingBackend(), targetFrame, options, clipPaths) + { + } + + /// + /// Initializes a new instance of the class with an explicit backend and initial state. + /// + /// The active processing configuration. + /// The drawing backend implementation. + /// The destination frame. + /// Initial drawing options for this canvas instance. + /// Initial clip paths for this canvas instance. + internal DrawingCanvas( + Configuration configuration, + IDrawingBackend backend, + ICanvasFrame targetFrame, + DrawingOptions options, + params IPath[] clipPaths) + : this( + configuration, + backend, + targetFrame, + new DrawingCanvasBatcher(configuration, backend, targetFrame), + new DrawingCanvasState(options, clipPaths)) + { + } + + /// + /// Initializes a new instance of the class + /// with explicit backend and batcher instances. + /// + /// The active processing configuration. + /// The drawing backend implementation. + /// The destination frame. + /// The command batcher used for deferred composition. + /// The default state used when no scoped state is active. + private DrawingCanvas( + Configuration configuration, + IDrawingBackend backend, + ICanvasFrame targetFrame, + DrawingCanvasBatcher batcher, + DrawingCanvasState defaultState) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(backend, nameof(backend)); + Guard.NotNull(targetFrame, nameof(targetFrame)); + Guard.NotNull(batcher, nameof(batcher)); + Guard.NotNull(defaultState, nameof(defaultState)); + + if (!targetFrame.TryGetCpuRegion(out _) && !targetFrame.TryGetNativeSurface(out _)) + { + throw new NotSupportedException("Canvas frame must expose either a CPU region or a native surface."); + } + + this.configuration = configuration; + this.backend = backend; + this.targetFrame = targetFrame; + this.batcher = batcher; + + // Canvas coordinates are local to the current frame; origin stays at (0,0). + this.Bounds = new Rectangle(0, 0, targetFrame.Bounds.Width, targetFrame.Bounds.Height); + this.savedStates.Push(defaultState); + } + + /// + public Rectangle Bounds { get; } + + /// + public int SaveCount => this.savedStates.Count; + + /// + /// Creates a drawing canvas over an existing frame. + /// + /// The frame backing the canvas. + /// Initial drawing options for this canvas instance. + /// Initial clip paths for this canvas instance. + /// A drawing canvas targeting . + public static DrawingCanvas FromFrame( + ImageFrame frame, + DrawingOptions options, + params IPath[] clipPaths) + { + Guard.NotNull(frame, nameof(frame)); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(clipPaths, nameof(clipPaths)); + + return new DrawingCanvas( + frame.Configuration, + new Buffer2DRegion(frame.PixelBuffer, frame.Bounds), + options, + clipPaths); + } + + /// + /// Creates a drawing canvas over a specific frame of an image. + /// + /// The image containing the frame. + /// The zero-based frame index to target. + /// Initial drawing options for this canvas instance. + /// Initial clip paths for this canvas instance. + /// A drawing canvas targeting the selected frame. + public static DrawingCanvas FromImage( + Image image, + int frameIndex, + DrawingOptions options, + params IPath[] clipPaths) + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(clipPaths, nameof(clipPaths)); + Guard.MustBeBetweenOrEqualTo(frameIndex, 0, image.Frames.Count - 1, nameof(frameIndex)); + + return FromFrame(image.Frames[frameIndex], options, clipPaths); + } + + /// + /// Creates a drawing canvas over the root frame of an image. + /// + /// The image whose root frame should be targeted. + /// Initial drawing options for this canvas instance. + /// Initial clip paths for this canvas instance. + /// A drawing canvas targeting the root frame. + public static DrawingCanvas FromRootFrame( + Image image, + DrawingOptions options, + params IPath[] clipPaths) + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(clipPaths, nameof(clipPaths)); + + return FromFrame(image.Frames.RootFrame, options, clipPaths); + } + + /// + public int Save() + { + this.EnsureNotDisposed(); + DrawingCanvasState current = this.ResolveState(); + + // Push a non-layer copy of the current state. + // Only states pushed by SaveLayer() should trigger layer compositing on restore. + this.savedStates.Push(new DrawingCanvasState(current.Options, current.ClipPaths)); + return this.savedStates.Count; + } + + /// + public int Save(DrawingOptions options, params IPath[] clipPaths) + => this.SaveCore(options, clipPaths); + + private int SaveCore(DrawingOptions options, IReadOnlyList clipPaths) + { + this.EnsureNotDisposed(); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(clipPaths, nameof(clipPaths)); + + _ = this.Save(); + DrawingCanvasState state = new(options, clipPaths); + _ = this.savedStates.Pop(); + this.savedStates.Push(state); + return this.savedStates.Count; + } + + /// + public int SaveLayer() + => this.SaveLayer(new GraphicsOptions()); + + /// + public int SaveLayer(GraphicsOptions layerOptions) + => this.SaveLayer(layerOptions, this.Bounds); + + /// + public int SaveLayer(GraphicsOptions layerOptions, Rectangle bounds) + { + this.EnsureNotDisposed(); + Guard.NotNull(layerOptions, nameof(layerOptions)); + + // Flush any pending commands to the current target before switching. + this.Flush(); + + // Clamp bounds to the canvas. + Rectangle layerBounds = Rectangle.Intersect(this.Bounds, bounds); + if (layerBounds.Width <= 0 || layerBounds.Height <= 0) + { + layerBounds = new Rectangle(0, 0, 1, 1); + } + + // Allocate a layer frame via the backend (CPU image or GPU texture). + ICanvasFrame currentTarget = this.batcher.TargetFrame; + ICanvasFrame layerFrame = this.backend.CreateLayerFrame( + this.configuration, + currentTarget, + layerBounds.Width, + layerBounds.Height); + + // Save the current batcher so we can restore it later. + DrawingCanvasBatcher parentBatcher = this.batcher; + LayerData layerData = new(parentBatcher, layerFrame, layerBounds); + this.layerDataStack.Push(layerData); + + // Redirect commands to the layer target. + this.batcher = new DrawingCanvasBatcher(this.configuration, this.backend, layerFrame); + + // Push a layer state onto the saved states stack. + DrawingCanvasState currentState = this.ResolveState(); + DrawingCanvasState layerState = new(currentState.Options, currentState.ClipPaths) + { + IsLayer = true, + LayerOptions = layerOptions, + LayerBounds = layerBounds, + }; + + this.savedStates.Push(layerState); + return this.savedStates.Count; + } + + /// + public void Restore() + { + this.EnsureNotDisposed(); + if (this.savedStates.Count <= 1) + { + return; + } + + DrawingCanvasState popped = this.savedStates.Pop(); + if (popped.IsLayer) + { + this.CompositeAndPopLayer(popped); + } + } + + /// + public void RestoreTo(int saveCount) + { + this.EnsureNotDisposed(); + Guard.MustBeBetweenOrEqualTo(saveCount, 1, this.savedStates.Count, nameof(saveCount)); + + while (this.savedStates.Count > saveCount) + { + DrawingCanvasState popped = this.savedStates.Pop(); + if (popped.IsLayer) + { + this.CompositeAndPopLayer(popped); + } + } + } + + /// + public DrawingCanvas CreateRegion(Rectangle region) + { + this.EnsureNotDisposed(); + + Rectangle clipped = Rectangle.Intersect(this.Bounds, region); + ICanvasFrame childFrame = new CanvasRegionFrame(this.targetFrame, clipped); + return new DrawingCanvas( + this.configuration, + this.backend, + childFrame, + this.batcher, + this.ResolveState()); + } + + /// + IDrawingCanvas IDrawingCanvas.CreateRegion(Rectangle region) + => this.CreateRegion(region); + + /// + public void Clear(Brush brush) + { + DrawingCanvasState state = this.ResolveState(); + DrawingOptions options = state.Options.CloneForClearOperation(); + this.ExecuteWithTemporaryState(options, state.ClipPaths, () => this.Fill(brush)); + } + + /// + public void Clear(Brush brush, Rectangle region) + { + DrawingCanvasState state = this.ResolveState(); + DrawingOptions options = state.Options.CloneForClearOperation(); + this.ExecuteWithTemporaryState(options, state.ClipPaths, () => this.Fill(brush, region)); + } + + /// + public void Clear(Brush brush, IPath path) + { + DrawingCanvasState state = this.ResolveState(); + DrawingOptions options = state.Options.CloneForClearOperation(); + this.ExecuteWithTemporaryState(options, state.ClipPaths, () => this.Fill(brush, path)); + } + + /// + public void Fill(Brush brush) + => this.Fill(brush, this.Bounds); + + /// + public void Fill(Brush brush, Rectangle region) + => this.Fill(brush, new RectangularPolygon(region.X, region.Y, region.Width, region.Height)); + + /// + public void Fill(Brush brush, IPathCollection paths) + { + Guard.NotNull(paths, nameof(paths)); + foreach (IPath path in paths) + { + this.Fill(brush, path); + } + } + + /// + public void Fill(Brush brush, PathBuilder pathBuilder) + { + Guard.NotNull(pathBuilder, nameof(pathBuilder)); + this.Fill(brush, pathBuilder.Build()); + } + + /// + public void Fill(Brush brush, IPath path) + { + this.EnsureNotDisposed(); + Guard.NotNull(path, nameof(path)); + Guard.NotNull(brush, nameof(brush)); + + DrawingCanvasState state = this.ResolveState(); + DrawingOptions effectiveOptions = state.Options; + + IPath closed = path.AsClosedPath(); + + Brush effectiveBrush = brush; + IPath effectivePath = closed; + if (effectiveOptions.Transform != Matrix4x4.Identity) + { + effectivePath = FlattenAndTransform(closed, effectiveOptions.Transform); + effectiveBrush = brush.Transform(effectiveOptions.Transform); + } + + effectivePath = ApplyClipPaths(effectivePath, effectiveOptions.ShapeOptions, state.ClipPaths); + + this.PrepareCompositionCore(effectivePath, effectiveBrush, effectiveOptions, RasterizerSamplingOrigin.PixelBoundary); + } + + /// + public void Process(Rectangle region, Action operation) + => this.Process(new RectangularPolygon(region.X, region.Y, region.Width, region.Height), operation); + + /// + public void Process(PathBuilder pathBuilder, Action operation) + { + Guard.NotNull(pathBuilder, nameof(pathBuilder)); + this.Process(pathBuilder.Build(), operation); + } + + /// + public void Process(IPath path, Action operation) + { + this.EnsureNotDisposed(); + Guard.NotNull(path, nameof(path)); + Guard.NotNull(operation, nameof(operation)); + + // This operation samples the current destination state. Flush queued commands first + // so readback observes strict draw-order semantics. + this.Flush(); + + DrawingCanvasState state = this.ResolveState(); + DrawingOptions effectiveOptions = state.Options; + + IPath closed = path.AsClosedPath(); + IPath transformedPath = effectiveOptions.Transform == Matrix4x4.Identity + ? closed + : FlattenAndTransform(closed, effectiveOptions.Transform); + transformedPath = ApplyClipPaths(transformedPath, effectiveOptions.ShapeOptions, state.ClipPaths); + + Rectangle sourceRect = ToConservativeBounds(transformedPath.Bounds); + sourceRect = Rectangle.Intersect(this.Bounds, sourceRect); + if (sourceRect.Width <= 0 || sourceRect.Height <= 0) + { + return; + } + + // Defensive guard: built-in backends should provide either direct readback (CPU/backed surface) + // or shadow fallback, but custom/inconsistent backend+target combinations can still fail both paths. + if (!this.TryCreateProcessSourceImage(sourceRect, out Image? sourceImage)) + { + throw new NotSupportedException("Canvas process operations require either CPU pixels, backend readback support, or shadow fallback."); + } + + sourceImage.Mutate(operation); + + Point brushOffset = new( + sourceRect.X - (int)MathF.Floor(transformedPath.Bounds.Left), + sourceRect.Y - (int)MathF.Floor(transformedPath.Bounds.Top)); + ImageBrush brush = new(sourceImage, sourceImage.Bounds, brushOffset); + + this.pendingImageResources.Add(sourceImage); + this.PrepareCompositionCore(transformedPath, brush, effectiveOptions, RasterizerSamplingOrigin.PixelBoundary); + } + + /// + public void DrawArc(Pen pen, PointF center, SizeF radius, float rotation, float startAngle, float sweepAngle) + => this.Draw(pen, new Path(new ArcLineSegment(center, radius, rotation, startAngle, sweepAngle))); + + /// + public void DrawBezier(Pen pen, params PointF[] points) + { + Guard.NotNull(points, nameof(points)); + this.Draw(pen, new Path(new CubicBezierLineSegment(points))); + } + + /// + public void DrawEllipse(Pen pen, PointF center, SizeF size) + => this.Draw(pen, new EllipsePolygon(center, size)); + + /// + public void DrawLine(Pen pen, params PointF[] points) + { + Guard.NotNull(points, nameof(points)); + this.Draw(pen, new Path(points)); + } + + /// + public void Draw(Pen pen, Rectangle region) + => this.Draw(pen, new RectangularPolygon(region.X, region.Y, region.Width, region.Height)); + + /// + public void Draw(Pen pen, IPathCollection paths) + { + Guard.NotNull(paths, nameof(paths)); + foreach (IPath path in paths) + { + this.Draw(pen, path); + } + } + + /// + public void Draw(Pen pen, PathBuilder pathBuilder) + { + Guard.NotNull(pathBuilder, nameof(pathBuilder)); + this.Draw(pen, pathBuilder.Build()); + } + + /// + public void Draw(Pen pen, IPath path) + { + this.EnsureNotDisposed(); + Guard.NotNull(pen, nameof(pen)); + Guard.NotNull(path, nameof(path)); + + DrawingCanvasState state = this.ResolveState(); + DrawingOptions effectiveOptions = state.Options; + + IPath transformedPath = effectiveOptions.Transform == Matrix4x4.Identity + ? path + : FlattenAndTransform(path, effectiveOptions.Transform); + + // Stroke geometry can self-overlap; non-zero winding preserves stroke semantics. + if (effectiveOptions.ShapeOptions.IntersectionRule != IntersectionRule.NonZero) + { + ShapeOptions shapeOptions = effectiveOptions.ShapeOptions.DeepClone(); + shapeOptions.IntersectionRule = IntersectionRule.NonZero; + effectiveOptions = new DrawingOptions(effectiveOptions.GraphicsOptions, shapeOptions, effectiveOptions.Transform); + } + + // When clip paths are active we must expand the stroke here so the clip + // boolean operation can be applied to the expanded outline geometry. + if (state.ClipPaths.Count > 0) + { + IPath outline = pen.GeneratePath(transformedPath); + outline = ApplyClipPaths(outline, effectiveOptions.ShapeOptions, state.ClipPaths); + this.PrepareCompositionCore(outline, pen.StrokeFill, effectiveOptions, RasterizerSamplingOrigin.PixelCenter); + return; + } + + this.PrepareStrokeCompositionCore( + transformedPath, + pen.StrokeFill, + pen.StrokeWidth, + pen.StrokeOptions, + pen.StrokePattern, + effectiveOptions); + } + + /// + public void DrawText( + RichTextOptions textOptions, + ReadOnlySpan text, + Brush? brush, + Pen? pen) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + return; + } + + DrawingCanvasState state = this.ResolveState(); + DrawingOptions effectiveOptions = state.Options; + + if (brush is null && pen is null) + { + throw new ArgumentException($"Expected a {nameof(brush)} or {nameof(pen)}. Both were null"); + } + + RichTextOptions configuredOptions = ConfigureTextOptions(textOptions); + using RichTextGlyphRenderer glyphRenderer = new(configuredOptions, effectiveOptions, pen, brush); + TextRenderer renderer = new(glyphRenderer); + renderer.RenderText(text, configuredOptions); + + this.DrawTextOperations(glyphRenderer.DrawingOperations, effectiveOptions, state.ClipPaths); + } + + /// + public void DrawGlyphs( + Brush brush, + Pen pen, + IReadOnlyList glyphs) + { + this.EnsureNotDisposed(); + Guard.NotNull(brush, nameof(brush)); + Guard.NotNull(pen, nameof(pen)); + Guard.NotNull(glyphs, nameof(glyphs)); + + DrawingCanvasState state = this.ResolveState(); + DrawingOptions baseOptions = state.Options; + IReadOnlyList clipPaths = state.ClipPaths; + + for (int glyphIndex = 0; glyphIndex < glyphs.Count; glyphIndex++) + { + GlyphPathCollection glyph = glyphs[glyphIndex]; + if (glyph.LayerCount == 0) + { + continue; + } + + if (glyph.LayerCount == 1) + { + this.Fill(brush, glyph.Paths); + continue; + } + + float glyphArea = glyph.Bounds.Width * glyph.Bounds.Height; + for (int layerIndex = 0; layerIndex < glyph.LayerCount; layerIndex++) + { + GlyphLayerInfo layer = glyph.Layers[layerIndex]; + if (layer.Count == 0) + { + continue; + } + + PathCollection layerPaths = glyph.GetLayerPaths(layerIndex); + DrawingOptions layerOptions = baseOptions.CloneOrReturnForRules( + layer.IntersectionRule, + layer.PixelAlphaCompositionMode, + layer.PixelColorBlendingMode); + + bool shouldFill; + if (layer.Kind is GlyphLayerKind.Decoration or GlyphLayerKind.Glyph) + { + shouldFill = true; + } + else + { + float layerArea = layerPaths.ComputeArea(); + shouldFill = layerArea > 0F && glyphArea > 0F && (layerArea / glyphArea) < 0.50F; + } + + this.ExecuteWithTemporaryState(layerOptions, clipPaths, () => + { + if (shouldFill) + { + this.Fill(brush, layerPaths); + } + else + { + this.Draw(pen, layerPaths); + } + }); + } + } + } + + /// + public RectangleF MeasureTextAdvance(RichTextOptions textOptions, ReadOnlySpan text) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + return RectangleF.Empty; + } + + FontRectangle advance = TextMeasurer.MeasureAdvance(text, textOptions); + return RectangleF.FromLTRB(advance.Left, advance.Top, advance.Right, advance.Bottom); + } + + /// + public RectangleF MeasureTextBounds(RichTextOptions textOptions, ReadOnlySpan text) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + return RectangleF.Empty; + } + + FontRectangle bounds = TextMeasurer.MeasureBounds(text, textOptions); + return RectangleF.FromLTRB(bounds.Left, bounds.Top, bounds.Right, bounds.Bottom); + } + + /// + public RectangleF MeasureTextRenderableBounds(RichTextOptions textOptions, ReadOnlySpan text) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + return RectangleF.Empty; + } + + FontRectangle renderableBounds = TextMeasurer.MeasureRenderableBounds(text, textOptions); + return RectangleF.FromLTRB(renderableBounds.Left, renderableBounds.Top, renderableBounds.Right, renderableBounds.Bottom); + } + + /// + public RectangleF MeasureTextSize(RichTextOptions textOptions, ReadOnlySpan text) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + return RectangleF.Empty; + } + + FontRectangle size = TextMeasurer.MeasureSize(text, textOptions); + return RectangleF.FromLTRB(size.Left, size.Top, size.Right, size.Bottom); + } + + /// + public bool TryMeasureCharacterAdvances(RichTextOptions textOptions, ReadOnlySpan text, out ReadOnlySpan advances) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + advances = []; + return false; + } + + return TextMeasurer.TryMeasureCharacterAdvances(text, textOptions, out advances); + } + + /// + public bool TryMeasureCharacterBounds(RichTextOptions textOptions, ReadOnlySpan text, out ReadOnlySpan bounds) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + bounds = []; + return false; + } + + return TextMeasurer.TryMeasureCharacterBounds(text, textOptions, out bounds); + } + + /// + public bool TryMeasureCharacterRenderableBounds(RichTextOptions textOptions, ReadOnlySpan text, out ReadOnlySpan bounds) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + bounds = []; + return false; + } + + return TextMeasurer.TryMeasureCharacterRenderableBounds(text, textOptions, out bounds); + } + + /// + public bool TryMeasureCharacterSizes(RichTextOptions textOptions, ReadOnlySpan text, out ReadOnlySpan sizes) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + sizes = []; + return false; + } + + return TextMeasurer.TryMeasureCharacterSizes(text, textOptions, out sizes); + } + + /// + public int CountTextLines(RichTextOptions textOptions, ReadOnlySpan text) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + return 0; + } + + return TextMeasurer.CountLines(text, textOptions); + } + + /// + public LineMetrics[] GetTextLineMetrics(RichTextOptions textOptions, ReadOnlySpan text) + { + this.EnsureNotDisposed(); + Guard.NotNull(textOptions, nameof(textOptions)); + + if (text.IsEmpty) + { + return []; + } + + return TextMeasurer.GetLineMetrics(text, textOptions); + } + + /// + void IDrawingCanvas.DrawImage( + Image image, + Rectangle sourceRect, + RectangleF destinationRect, + IResampler? sampler) + { + this.EnsureNotDisposed(); + Guard.NotNull(image, nameof(image)); + + if (image is Image specificImage) + { + this.DrawImageCore(specificImage, sourceRect, destinationRect, sampler, ownsSourceImage: false); + return; + } + + Image convertedImage = image.CloneAs(); + this.DrawImageCore(convertedImage, sourceRect, destinationRect, sampler, ownsSourceImage: true); + } + + /// + public void DrawImage( + Image image, + Rectangle sourceRect, + RectangleF destinationRect, + IResampler? sampler = null) + => this.DrawImageCore(image, sourceRect, destinationRect, sampler, ownsSourceImage: false); + + private void DrawImageCore( + Image image, + Rectangle sourceRect, + RectangleF destinationRect, + IResampler? sampler, + bool ownsSourceImage) + { + this.EnsureNotDisposed(); + Guard.NotNull(image, nameof(image)); + bool disposeSourceImage = ownsSourceImage; + + DrawingCanvasState state = this.ResolveState(); + DrawingOptions effectiveOptions = state.Options; + + if (sourceRect.Width <= 0 || + sourceRect.Height <= 0 || + destinationRect.Width <= 0 || + destinationRect.Height <= 0) + { + return; + } + + Rectangle clippedSourceRect = Rectangle.Intersect(sourceRect, image.Bounds); + if (clippedSourceRect.Width <= 0 || clippedSourceRect.Height <= 0) + { + return; + } + + RectangleF clippedDestinationRect = MapSourceClipToDestination(sourceRect, destinationRect, clippedSourceRect); + if (clippedDestinationRect.Width <= 0 || clippedDestinationRect.Height <= 0) + { + return; + } + + Size scaledSize = new( + Math.Max(1, (int)MathF.Ceiling(clippedDestinationRect.Width)), + Math.Max(1, (int)MathF.Ceiling(clippedDestinationRect.Height))); + + bool requiresScaling = + clippedSourceRect.Width != scaledSize.Width || + clippedSourceRect.Height != scaledSize.Height; + + Image brushImage = image; + RectangleF brushImageRegion = clippedSourceRect; + RectangleF renderDestinationRect = clippedDestinationRect; + Image? ownedImage = null; + + try + { + // Phase 1: Prepare source pixels (crop/scale) in image-local space. + if (requiresScaling) + { + ownedImage = CreateScaledDrawImage(image, clippedSourceRect, scaledSize, sampler); + brushImage = ownedImage; + brushImageRegion = ownedImage.Bounds; + } + else if (clippedSourceRect != image.Bounds) + { + ownedImage = image.Clone(ctx => ctx.Crop(clippedSourceRect)); + brushImage = ownedImage; + brushImageRegion = ownedImage.Bounds; + } + + // Phase 2: Apply canvas transform to image content when requested. + if (effectiveOptions.Transform != Matrix4x4.Identity) + { + Image transformed = CreateTransformedDrawImage( + brushImage, + clippedDestinationRect, + effectiveOptions.Transform, + sampler, + out renderDestinationRect); + + ownedImage?.Dispose(); + ownedImage = transformed; + brushImage = transformed; + brushImageRegion = transformed.Bounds; + } + + if (renderDestinationRect.Width <= 0 || renderDestinationRect.Height <= 0) + { + return; + } + + // Phase 3: Transfer temp-image ownership to deferred batch execution. + if (!ReferenceEquals(brushImage, image)) + { + if (disposeSourceImage) + { + image.Dispose(); + disposeSourceImage = false; + } + + this.pendingImageResources.Add(brushImage); + ownedImage = null; + } + else if (disposeSourceImage) + { + this.pendingImageResources.Add(image); + disposeSourceImage = false; + } + + ImageBrush brush = new(brushImage, brushImageRegion); + IPath destinationPath = new RectangularPolygon( + renderDestinationRect.X, + renderDestinationRect.Y, + renderDestinationRect.Width, + renderDestinationRect.Height); + + this.Fill(brush, destinationPath); + } + finally + { + ownedImage?.Dispose(); + if (disposeSourceImage) + { + image.Dispose(); + } + } + } + + /// + /// Prepares a path fill composition command and enqueues it in the batcher. + /// + /// Path to fill. + /// Brush used for shading. + /// Effective drawing options. + /// Rasterizer sampling origin. + private void PrepareCompositionCore( + IPath path, + Brush brush, + DrawingOptions options, + RasterizerSamplingOrigin samplingOrigin) + { + GraphicsOptions graphicsOptions = options.GraphicsOptions; + ShapeOptions shapeOptions = options.ShapeOptions; + RasterizationMode rasterizationMode = graphicsOptions.Antialias ? RasterizationMode.Antialiased : RasterizationMode.Aliased; + + RectangleF bounds = path.Bounds; + if (samplingOrigin == RasterizerSamplingOrigin.PixelCenter) + { + // Keep rasterizer interest aligned with center-sampled scan conversion. + bounds = new RectangleF(bounds.X + 0.5F, bounds.Y + 0.5F, bounds.Width, bounds.Height); + } + + Rectangle interest = Rectangle.FromLTRB( + (int)MathF.Floor(bounds.Left), + (int)MathF.Floor(bounds.Top), + (int)MathF.Ceiling(bounds.Right), + (int)MathF.Ceiling(bounds.Bottom)); + + RasterizerOptions rasterizerOptions = new( + interest, + shapeOptions.IntersectionRule, + rasterizationMode, + samplingOrigin, + graphicsOptions.AntialiasThreshold); + + this.batcher.AddComposition( + CompositionCommand.Create( + path, + brush, + graphicsOptions, + rasterizerOptions, + this.targetFrame.Bounds.Location)); + } + + /// + /// Prepares a stroke composition command with the original centerline path and enqueues it. + /// The backend is responsible for stroke expansion or SDF evaluation. + /// + /// Original centerline path in target-local coordinates. + /// Brush used for shading. + /// Stroke width in pixels. + /// Stroke geometry options. + /// Optional dash pattern. + /// Effective drawing options. + private void PrepareStrokeCompositionCore( + IPath path, + Brush brush, + float strokeWidth, + StrokeOptions strokeOptions, + ReadOnlyMemory strokePattern, + DrawingOptions options) + { + GraphicsOptions graphicsOptions = options.GraphicsOptions; + ShapeOptions shapeOptions = options.ShapeOptions; + RasterizationMode rasterizationMode = graphicsOptions.Antialias ? RasterizationMode.Antialiased : RasterizationMode.Aliased; + + // Inflate path bounds by the maximum possible stroke extent. + // The miter limit caps the tip extension; the base half-width is always present. + float halfWidth = strokeWidth / 2f; + float maxExtent = halfWidth * (float)Math.Max(strokeOptions.MiterLimit, 1D); + RectangleF bounds = path.Bounds; + bounds = new RectangleF( + bounds.X - maxExtent + 0.5F, + bounds.Y - maxExtent + 0.5F, + bounds.Width + (maxExtent * 2f), + bounds.Height + (maxExtent * 2f)); + + Rectangle interest = Rectangle.FromLTRB( + (int)MathF.Floor(bounds.Left), + (int)MathF.Floor(bounds.Top), + (int)MathF.Ceiling(bounds.Right), + (int)MathF.Ceiling(bounds.Bottom)); + + RasterizerOptions rasterizerOptions = new( + interest, + shapeOptions.IntersectionRule, + rasterizationMode, + RasterizerSamplingOrigin.PixelCenter, + graphicsOptions.AntialiasThreshold); + + this.batcher.AddComposition( + CompositionCommand.CreateStroke( + path, + brush, + graphicsOptions, + rasterizerOptions, + strokeOptions, + strokeWidth, + strokePattern, + this.targetFrame.Bounds.Location)); + } + + /// + /// Converts rendered text operations to composition commands and submits them to the batcher. + /// + /// Text drawing operations produced by glyph layout/rendering. + /// Drawing options applied to each operation. + /// Clip paths resolved from effective canvas state. + private void DrawTextOperations( + List operations, + DrawingOptions drawingOptions, + IReadOnlyList clipPaths) + { + this.EnsureNotDisposed(); + + // Build composition commands and enforce render-pass ordering while preserving + // original emission order inside each pass. This preserves overlapping color-font + // layer compositing semantics (for example emoji mouth/teeth layers). + Dictionary definitionKeyCache = []; + List<(byte RenderPass, int Sequence, CompositionCommand Command)> entries = new(operations.Count); + for (int i = 0; i < operations.Count; i++) + { + DrawingOperation operation = operations[i]; + DrawingOperation clippedOperation = operation; + clippedOperation.Path = ApplyClipPaths(operation.Path, drawingOptions.ShapeOptions, clipPaths); + entries.Add((operation.RenderPass, i, this.CreateCompositionCommand(clippedOperation, drawingOptions, definitionKeyCache))); + } + + entries.Sort(static (a, b) => + { + int cmp = a.RenderPass.CompareTo(b.RenderPass); + return cmp != 0 ? cmp : a.Sequence.CompareTo(b.Sequence); + }); + + for (int i = 0; i < entries.Count; i++) + { + this.batcher.AddComposition(entries[i].Command); + } + } + + /// + /// Resolves the currently active drawing state. + /// + /// The current state. + private DrawingCanvasState ResolveState() => this.savedStates.Peek(); + + /// + /// Executes an action with a temporary scoped state, restoring the previous scoped state afterwards. + /// + /// Temporary drawing options. + /// Temporary clip paths. + /// Action to execute. + private void ExecuteWithTemporaryState(DrawingOptions options, IReadOnlyList clipPaths, Action action) + { + int saveCount = this.savedStates.Count; + _ = this.SaveCore(options, clipPaths); + try + { + action(); + } + finally + { + this.RestoreTo(saveCount); + } + } + + /// + /// Attempts to create a source image for process-in-path operations. + /// The backend copies pixels directly into the image's pixel buffer — single copy. + /// + /// Source rectangle in local canvas coordinates. + /// The readback image when available. + /// when source pixels were resolved. + private bool TryCreateProcessSourceImage(Rectangle sourceRect, [NotNullWhen(true)] out Image? sourceImage) + { + sourceImage = new Image(this.configuration, sourceRect.Width, sourceRect.Height); + if (!this.backend.TryReadRegion(this.configuration, this.targetFrame, sourceRect, sourceImage.Frames.RootFrame.PixelBuffer)) + { + sourceImage.Dispose(); + sourceImage = null; + return false; + } + + return true; + } + + /// + /// Flattens the path first (reusing any cached curve subdivision), then transforms + /// the resulting flat points. This avoids discarding cached + /// subdivision data that would throw away. + /// + /// + /// Flattens a path into linear segments, then transforms the resulting points in place. + /// This avoids redundant curve subdivision that would occur if we transformed the original + /// path first (which discards cached flattening) and then flattened again. + /// + /// The path to flatten and transform. The original path is not mutated. + /// The transform matrix to apply to the flattened points. + /// + /// A pre-flattened whose points are already transformed. + /// The returned path owns its point buffers and may mutate them on subsequent transforms. + /// + private static IPath FlattenAndTransform(IPath path, Matrix4x4 matrix) + { + List<(PointF[] Points, RectangleF Bounds, bool IsClosed)>? subPaths = null; + bool allClosed = true; + float minX = float.MaxValue, minY = float.MaxValue; + float maxX = float.MinValue, maxY = float.MinValue; + + foreach (ISimplePath sp in path.Flatten()) + { + ReadOnlySpan srcPoints = sp.Points.Span; + if (srcPoints.Length < 2) + { + continue; + } + + PointF[] dstPoints = new PointF[srcPoints.Length]; + float spMinX = float.MaxValue, spMinY = float.MaxValue; + float spMaxX = float.MinValue, spMaxY = float.MinValue; + + for (int i = 0; i < srcPoints.Length; i++) + { + ref PointF dst = ref dstPoints[i]; + dst = PointF.Transform(srcPoints[i], matrix); + + if (dst.X < spMinX) + { + spMinX = dst.X; + } + + if (dst.Y < spMinY) + { + spMinY = dst.Y; + } + + if (dst.X > spMaxX) + { + spMaxX = dst.X; + } + + if (dst.Y > spMaxY) + { + spMaxY = dst.Y; + } + } + + RectangleF spBounds = new(spMinX, spMinY, spMaxX - spMinX, spMaxY - spMinY); + subPaths ??= []; + subPaths.Add((dstPoints, spBounds, sp.IsClosed)); + allClosed &= sp.IsClosed; + + if (spMinX < minX) + { + minX = spMinX; + } + + if (spMinY < minY) + { + minY = spMinY; + } + + if (spMaxX > maxX) + { + maxX = spMaxX; + } + + if (spMaxY > maxY) + { + maxY = spMaxY; + } + } + + if (subPaths is null) + { + return Path.Empty; + } + + RectangleF totalBounds = new(minX, minY, maxX - minX, maxY - minY); + if (allClosed) + { + // Fill path: enforce orientation for NonZero fill rule (ring 0 positive, ring 1+ negative). + if (subPaths.Count == 1) + { + PolygonUtilities.EnsureOrientation(subPaths[0].Points, 1); + return new FlattenedPath(subPaths[0].Points, true, subPaths[0].Bounds); + } + + FlattenedPath[] closed = new FlattenedPath[subPaths.Count]; + for (int i = 0; i < subPaths.Count; i++) + { + PolygonUtilities.EnsureOrientation(subPaths[i].Points, i == 0 ? 1 : -1); + closed[i] = new FlattenedPath(subPaths[i].Points, true, subPaths[i].Bounds); + } + + return new FlattenedCompositePath(closed, totalBounds); + } + + // Stroke centerline: preserve open/closed as-is, no orientation enforcement. + if (subPaths.Count == 1) + { + (PointF[] pts, RectangleF bounds, bool isClosed) = subPaths[0]; + return new FlattenedPath(pts, isClosed, bounds); + } + + // Multiple sub-paths with at least one open — return as a simple wrapper. + // This case is rare (multi-contour stroke centerlines). + FlattenedPath[] parts = new FlattenedPath[subPaths.Count]; + for (int i = 0; i < subPaths.Count; i++) + { + (PointF[] pts, RectangleF bounds, bool isClosed) = subPaths[i]; + parts[i] = new FlattenedPath(pts, isClosed, bounds); + } + + return new FlattenedCompositePath(parts, totalBounds); + } + + /// + /// Applies all clip paths to a subject path using the provided shape options. + /// + /// Path to clip. + /// Shape options used for clipping. + /// Clip paths to apply. + /// The clipped path. + private static IPath ApplyClipPaths(IPath subjectPath, ShapeOptions shapeOptions, IReadOnlyList clipPaths) + { + if (clipPaths.Count == 0) + { + return subjectPath; + } + + return subjectPath.Clip(shapeOptions, clipPaths); + } + + /// + public void Flush() + { + this.EnsureNotDisposed(); + try + { + this.batcher.FlushCompositions(); + } + finally + { + this.DisposePendingImageResources(); + } + } + + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + try + { + // Composite any active layers back before final flush. + while (this.layerDataStack.Count > 0) + { + this.Flush(); + LayerData layerData = this.layerDataStack.Pop(); + this.batcher = layerData.ParentBatcher; + ICanvasFrame destination = this.batcher.TargetFrame; + this.backend.ComposeLayer( + this.configuration, + layerData.LayerFrame, + destination, + layerData.LayerBounds.Location, + new GraphicsOptions()); + this.backend.ReleaseFrameResources(this.configuration, layerData.LayerFrame); + } + + this.batcher.FlushCompositions(); + } + finally + { + this.DisposePendingImageResources(); + + this.isDisposed = true; + } + } + + /// + /// Ensures this instance is not disposed. + /// + private void EnsureNotDisposed() + => ObjectDisposedException.ThrowIf(this.isDisposed, this); + + /// + /// Flushes the current layer batcher, composites the layer onto its parent target, + /// restores the parent batcher, and disposes the layer resources. + /// + /// The layer state that was just popped. + private void CompositeAndPopLayer(DrawingCanvasState layerState) + { + // Flush pending commands to the layer surface. + this.Flush(); + + LayerData layerData = this.layerDataStack.Pop(); + + // Restore the parent batcher. + this.batcher = layerData.ParentBatcher; + + // Composite the layer onto the parent batcher's target (which may be another layer + // in the case of nested SaveLayer calls, or the root target frame). + ICanvasFrame destination = this.batcher.TargetFrame; + GraphicsOptions options = layerState.LayerOptions ?? new GraphicsOptions(); + Rectangle bounds = layerState.LayerBounds ?? this.Bounds; + this.backend.ComposeLayer( + this.configuration, + layerData.LayerFrame, + destination, + bounds.Location, + options); + + this.backend.ReleaseFrameResources(this.configuration, layerData.LayerFrame); + } + + /// + /// Normalizes text options to avoid applying origin translation twice when path-based text is used. + /// + /// Input text options. + /// Normalized text options for rendering. + private static RichTextOptions ConfigureTextOptions(RichTextOptions options) + { + if (options.Path is not null && options.Origin != Vector2.Zero) + { + // Path-based text uses the path itself as positioning source; fold origin into the path + // to avoid applying both path layout and origin translation. + return new RichTextOptions(options) + { + Origin = Vector2.Zero, + Path = options.Path.Translate(options.Origin) + }; + } + + return options; + } + + /// + /// Builds a normalized composition command for a text drawing operation. + /// + /// The source drawing operation. + /// Drawing options applied to the operation. + /// Optional cache used to reuse definition key computations. + /// A composition command ready for batching. + private CompositionCommand CreateCompositionCommand( + DrawingOperation operation, + DrawingOptions drawingOptions, + Dictionary? definitionKeyCache = null) + { + Brush compositeBrush = operation.Kind == DrawingOperationKind.Fill + ? operation.Brush! + : operation.Pen!.StrokeFill; + + GraphicsOptions graphicsOptions = + drawingOptions.GraphicsOptions.CloneOrReturnForRules( + operation.PixelAlphaCompositionMode, + operation.PixelColorBlendingMode); + + RasterizationMode rasterizationMode = graphicsOptions.Antialias + ? RasterizationMode.Antialiased + : RasterizationMode.Aliased; + + Point destinationOffset = new( + this.targetFrame.Bounds.X + operation.RenderLocation.X, + this.targetFrame.Bounds.Y + operation.RenderLocation.Y); + + if (operation.Kind == DrawingOperationKind.Draw) + { + Pen pen = operation.Pen!; + IPath path = operation.Path; + + // Stroke geometry can self-overlap; non-zero winding preserves stroke semantics. + IntersectionRule intersectionRule = operation.IntersectionRule != IntersectionRule.NonZero + ? IntersectionRule.NonZero + : operation.IntersectionRule; + + float halfWidth = pen.StrokeWidth / 2f; + float maxExtent = halfWidth * (float)Math.Max(pen.StrokeOptions.MiterLimit, 1D); + RectangleF bounds = path.Bounds; + bounds = new RectangleF( + bounds.X - maxExtent + 0.5F, + bounds.Y - maxExtent + 0.5F, + bounds.Width + (maxExtent * 2f), + bounds.Height + (maxExtent * 2f)); + + Rectangle interest = Rectangle.FromLTRB( + (int)MathF.Floor(bounds.Left), + (int)MathF.Floor(bounds.Top), + (int)MathF.Ceiling(bounds.Right), + (int)MathF.Ceiling(bounds.Bottom)); + + RasterizerOptions rasterizerOptions = new( + interest, + intersectionRule, + rasterizationMode, + RasterizerSamplingOrigin.PixelCenter, + graphicsOptions.AntialiasThreshold); + + return CompositionCommand.CreateStroke( + path, + compositeBrush, + graphicsOptions, + rasterizerOptions, + pen.StrokeOptions, + pen.StrokeWidth, + pen.StrokePattern, + destinationOffset, + definitionKeyCache); + } + else + { + IPath compositionPath = operation.Path; + RectangleF bounds = compositionPath.Bounds; + + Rectangle interest = Rectangle.FromLTRB( + (int)MathF.Floor(bounds.Left), + (int)MathF.Floor(bounds.Top), + (int)MathF.Ceiling(bounds.Right), + (int)MathF.Ceiling(bounds.Bottom)); + + RasterizerOptions rasterizerOptions = new( + interest, + operation.IntersectionRule, + rasterizationMode, + RasterizerSamplingOrigin.PixelBoundary, + graphicsOptions.AntialiasThreshold); + + return CompositionCommand.Create( + compositionPath, + compositeBrush, + graphicsOptions, + rasterizerOptions, + destinationOffset, + definitionKeyCache); + } + } + + /// + /// Converts floating bounds to a conservative integer rectangle using floor/ceiling. + /// + /// The floating bounds to convert. + /// A rectangle covering the full floating bounds extent. + private static Rectangle ToConservativeBounds(RectangleF bounds) + => Rectangle.FromLTRB( + (int)MathF.Floor(bounds.Left), + (int)MathF.Floor(bounds.Top), + (int)MathF.Ceiling(bounds.Right), + (int)MathF.Ceiling(bounds.Bottom)); + + /// + /// Creates resize options used for image drawing operations. + /// + /// Requested output size. + /// Optional resampler. Defaults to bicubic. + /// A resize options instance configured for stretch behavior. + private static ResizeOptions CreateDrawImageResizeOptions(Size size, IResampler? sampler) + => new() + { + Size = size, + Mode = ResizeMode.Stretch, + Sampler = sampler ?? KnownResamplers.Bicubic + }; + + /// + /// Creates a scaled image for drawing, optionally cropping to a source region first. + /// + /// The source image. + /// The clipped source rectangle. + /// The target scaled size. + /// Optional resampler used for scaling. + /// A new image containing the scaled pixels. + private static Image CreateScaledDrawImage( + Image image, + Rectangle clippedSourceRect, + Size scaledSize, + IResampler? sampler) + { + ResizeOptions effectiveResizeOptions = CreateDrawImageResizeOptions(scaledSize, sampler); + if (clippedSourceRect == image.Bounds) + { + return image.Clone(ctx => ctx.Resize(effectiveResizeOptions)); + } + + Image result = image.Clone(ctx => ctx.Crop(clippedSourceRect)); + result.Mutate(ctx => ctx.Resize(effectiveResizeOptions)); + return result; + } + + /// + /// Applies a transform to image content and returns the transformed image. + /// + /// The source image. + /// Destination rectangle in canvas coordinates. + /// Canvas transform to apply. + /// Optional resampler used during transform. + /// Receives the transformed destination bounds. + /// A new image containing transformed pixels. + private static Image CreateTransformedDrawImage( + Image image, + RectangleF destinationRect, + Matrix4x4 transform, + IResampler? sampler, + out RectangleF transformedDestinationRect) + { + // Source space: pixel coordinates in the untransformed source image (0..Width, 0..Height). + // Destination space: where that image would land on the canvas without any extra transform. + // This matrix maps source -> destination by scaling to destination size then translating to destination origin. + Matrix4x4 sourceToDestination = Matrix4x4.CreateScale( + destinationRect.Width / image.Width, + destinationRect.Height / image.Height, + 1) + * Matrix4x4.CreateTranslation(destinationRect.X, destinationRect.Y, 0); + + // Apply the canvas transform after source->destination placement: + // source -> destination -> transformed-canvas. + Matrix4x4 sourceToTransformedCanvas = sourceToDestination * transform; + + // Compute the transformed axis-aligned bounds so we know how large the output bitmap must be. + transformedDestinationRect = TransformRectangle( + new RectangleF(0, 0, image.Width, image.Height), + sourceToTransformedCanvas); + + // The transform can produce fractional/max bounds; round up to whole pixels for target allocation. + Size targetSize = new( + Math.Max(1, (int)MathF.Ceiling(transformedDestinationRect.Width)), + Math.Max(1, (int)MathF.Ceiling(transformedDestinationRect.Height))); + + // ImageSharp.Transform expects output coordinates relative to the output bitmap origin (0,0). + // Shift transformed-canvas coordinates so transformedDestinationRect.Left/Top becomes 0,0. + Matrix4x4 sourceToTarget = sourceToTransformedCanvas + * Matrix4x4.CreateTranslation(-transformedDestinationRect.X, -transformedDestinationRect.Y, 0); + + // Resample source pixels into the target bitmap using the computed source->target mapping. + return image.Clone(ctx => ctx.Transform( + image.Bounds, + sourceToTarget, + targetSize, + sampler ?? KnownResamplers.Bicubic)); + } + + /// + /// Maps a clipped source rectangle back to the corresponding destination rectangle. + /// + /// Original source rectangle. + /// Original destination rectangle. + /// Source rectangle clipped to image bounds. + /// The destination rectangle corresponding to the clipped source region. + private static RectangleF MapSourceClipToDestination( + Rectangle sourceRect, + RectangleF destinationRect, + Rectangle clippedSourceRect) + { + float scaleX = destinationRect.Width / sourceRect.Width; + float scaleY = destinationRect.Height / sourceRect.Height; + + float left = destinationRect.Left + ((clippedSourceRect.Left - sourceRect.Left) * scaleX); + float top = destinationRect.Top + ((clippedSourceRect.Top - sourceRect.Top) * scaleY); + float width = clippedSourceRect.Width * scaleX; + float height = clippedSourceRect.Height * scaleY; + + return new RectangleF(left, top, width, height); + } + + /// + /// Computes the axis-aligned bounding rectangle of a transformed rectangle. + /// + /// Input rectangle. + /// Transform matrix. + /// Axis-aligned bounds of the transformed rectangle. + private static RectangleF TransformRectangle(RectangleF rectangle, Matrix4x4 matrix) + { + PointF topLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Top), matrix); + PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix); + PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix); + PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + + float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X)); + float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y)); + float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X)); + float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y)); + + return RectangleF.FromLTRB(left, top, right, bottom); + } + + /// + /// Disposes image resources retained for deferred draw execution. + /// + private void DisposePendingImageResources() + { + if (this.pendingImageResources.Count == 0) + { + return; + } + + // Release deferred image resources once queued operations have executed. + for (int i = 0; i < this.pendingImageResources.Count; i++) + { + this.pendingImageResources[i].Dispose(); + } + + this.pendingImageResources.Clear(); + } +} diff --git a/src/ImageSharp.Drawing/Processing/DrawingOperation.cs b/src/ImageSharp.Drawing/Processing/DrawingOperation.cs new file mode 100644 index 000000000..d98eac94f --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/DrawingOperation.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing; + +internal enum DrawingOperationKind : byte +{ + Fill = 0, + Draw = 1 +} + +internal struct DrawingOperation +{ + public DrawingOperationKind Kind { get; set; } + + public IPath Path { get; set; } + + public Point RenderLocation { get; set; } + + public IntersectionRule IntersectionRule { get; set; } + + public byte RenderPass { get; set; } + + public Brush? Brush { get; set; } + + public Pen? Pen { get; set; } + + public PixelAlphaCompositionMode PixelAlphaCompositionMode { get; set; } + + public PixelColorBlendingMode PixelColorBlendingMode { get; set; } +} diff --git a/src/ImageSharp.Drawing/Processing/DrawingOptions.cs b/src/ImageSharp.Drawing/Processing/DrawingOptions.cs index a0f4f959b..8924f629c 100644 --- a/src/ImageSharp.Drawing/Processing/DrawingOptions.cs +++ b/src/ImageSharp.Drawing/Processing/DrawingOptions.cs @@ -6,7 +6,8 @@ namespace SixLabors.ImageSharp.Drawing.Processing; /// -/// Options for influencing the drawing functions. +/// Provides options for influencing drawing operations, combining graphics rendering settings, +/// shape fill-rule behavior, and an optional coordinate transform. /// public class DrawingOptions { @@ -20,13 +21,13 @@ public DrawingOptions() { this.graphicsOptions = new GraphicsOptions(); this.shapeOptions = new ShapeOptions(); - this.Transform = Matrix3x2.Identity; + this.Transform = Matrix4x4.Identity; } internal DrawingOptions( GraphicsOptions graphicsOptions, ShapeOptions shapeOptions, - Matrix3x2 transform) + Matrix4x4 transform) { DebugGuard.NotNull(graphicsOptions, nameof(graphicsOptions)); DebugGuard.NotNull(shapeOptions, nameof(shapeOptions)); @@ -37,7 +38,8 @@ internal DrawingOptions( } /// - /// Gets or sets the Graphics Options. + /// Gets or sets the graphics rendering options that control antialiasing, blending, alpha composition, + /// and coverage thresholding for the drawing operation. /// public GraphicsOptions GraphicsOptions { @@ -50,7 +52,7 @@ public GraphicsOptions GraphicsOptions } /// - /// Gets or sets the Shape Options. + /// Gets or sets the shape options that control fill-rule intersection mode and boolean clipping behavior. /// public ShapeOptions ShapeOptions { @@ -63,7 +65,9 @@ public ShapeOptions ShapeOptions } /// - /// Gets or sets the Transform to apply during rasterization. + /// Gets or sets the affine transform matrix applied to vector geometry before rasterization. + /// Can be used to translate, rotate, scale, or skew shapes. + /// Defaults to . /// - public Matrix3x2 Transform { get; set; } + public Matrix4x4 Transform { get; set; } } diff --git a/src/ImageSharp.Drawing/Processing/DrawingOptionsDefaultsExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawingOptionsDefaultsExtensions.cs index 4d4c66737..942ae2318 100644 --- a/src/ImageSharp.Drawing/Processing/DrawingOptionsDefaultsExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/DrawingOptionsDefaultsExtensions.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Drawing.Processing; /// public static class DrawingOptionsDefaultsExtensions { - private const string DrawingTransformMatrixKey = "DrawingTransformMatrix3x2"; + private const string DrawingTransformMatrixKey = "DrawingTransformMatrix4x4"; /// /// Gets the default shape processing options against The source image processing context. @@ -26,7 +26,7 @@ public static DrawingOptions GetDrawingOptions(this IImageProcessingContext cont /// The image processing context to store default against. /// The matrix to use. /// The passed in to allow chaining. - public static IImageProcessingContext SetDrawingTransform(this IImageProcessingContext context, Matrix3x2 matrix) + public static IImageProcessingContext SetDrawingTransform(this IImageProcessingContext context, Matrix4x4 matrix) { context.Properties[DrawingTransformMatrixKey] = matrix; return context; @@ -37,7 +37,7 @@ public static IImageProcessingContext SetDrawingTransform(this IImageProcessingC /// /// The configuration to store default against. /// The default matrix to use. - public static void SetDrawingTransform(this Configuration configuration, Matrix3x2 matrix) + public static void SetDrawingTransform(this Configuration configuration, Matrix4x4 matrix) => configuration.Properties[DrawingTransformMatrixKey] = matrix; /// @@ -45,9 +45,9 @@ public static void SetDrawingTransform(this Configuration configuration, Matrix3 /// /// The image processing context to retrieve defaults from. /// The matrix. - public static Matrix3x2 GetDrawingTransform(this IImageProcessingContext context) + public static Matrix4x4 GetDrawingTransform(this IImageProcessingContext context) { - if (context.Properties.TryGetValue(DrawingTransformMatrixKey, out object? options) && options is Matrix3x2 go) + if (context.Properties.TryGetValue(DrawingTransformMatrixKey, out object? options) && options is Matrix4x4 go) { return go; } @@ -62,13 +62,28 @@ public static Matrix3x2 GetDrawingTransform(this IImageProcessingContext context /// /// The configuration to retrieve defaults from. /// The globally configured default matrix. - public static Matrix3x2 GetDrawingTransform(this Configuration configuration) + public static Matrix4x4 GetDrawingTransform(this Configuration configuration) { - if (configuration.Properties.TryGetValue(DrawingTransformMatrixKey, out object? options) && options is Matrix3x2 go) + if (configuration.Properties.TryGetValue(DrawingTransformMatrixKey, out object? options) && options is Matrix4x4 go) { return go; } - return Matrix3x2.Identity; + return Matrix4x4.Identity; + } + + /// + /// Clones the path graphic options and applies changes required to force clearing. + /// + /// The drawing options to clone + /// A clone of shapeOptions with ColorBlendingMode, AlphaCompositionMode, and BlendPercentage set + public static DrawingOptions CloneForClearOperation(this DrawingOptions drawingOptions) + { + GraphicsOptions options = drawingOptions.GraphicsOptions.DeepClone(); + options.ColorBlendingMode = PixelColorBlendingMode.Normal; + options.AlphaCompositionMode = PixelAlphaCompositionMode.Src; + options.BlendPercentage = 1F; + + return new DrawingOptions(options, drawingOptions.ShapeOptions, drawingOptions.Transform); } } diff --git a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs index fbe4233f0..73f571a5f 100644 --- a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Drawing.Processing; @@ -13,12 +14,6 @@ namespace SixLabors.ImageSharp.Drawing.Processing; /// public sealed class EllipticGradientBrush : GradientBrush { - private readonly PointF center; - - private readonly PointF referenceAxisEnd; - - private readonly float axisRatio; - /// /// The center of the elliptical gradient and 0 for the color stops. /// The end point of the reference axis of the ellipse. @@ -37,90 +32,112 @@ public EllipticGradientBrush( params ColorStop[] colorStops) : base(repetitionMode, colorStops) { - this.center = center; - this.referenceAxisEnd = referenceAxisEnd; - this.axisRatio = axisRatio; + this.Center = center; + this.ReferenceAxisEnd = referenceAxisEnd; + this.AxisRatio = axisRatio; + } + + /// + /// Gets the center of the ellipse. + /// + public PointF Center { get; } + + /// + /// Gets the end point of the reference axis. + /// + public PointF ReferenceAxisEnd { get; } + + /// + /// Gets the ratio of the secondary axis to the primary axis. + /// + public float AxisRatio { get; } + + /// + public override Brush Transform(Matrix4x4 matrix) + { + PointF tc = PointF.Transform(this.Center, matrix); + PointF tRef = PointF.Transform(this.ReferenceAxisEnd, matrix); + + // Compute a point on the perpendicular (secondary) axis and transform it. + float refDx = this.ReferenceAxisEnd.X - this.Center.X; + float refDy = this.ReferenceAxisEnd.Y - this.Center.Y; + float refLen = MathF.Sqrt((refDx * refDx) + (refDy * refDy)); + float secondLen = refLen * this.AxisRatio; + + // Perpendicular direction (rotated 90 degrees). + PointF secondEnd = new( + this.Center.X + (-refDy / refLen * secondLen), + this.Center.Y + (refDx / refLen * secondLen)); + PointF tSec = PointF.Transform(secondEnd, matrix); + + // Derive new ratio from transformed lengths. + float newRefLen = MathF.Sqrt( + ((tRef.X - tc.X) * (tRef.X - tc.X)) + ((tRef.Y - tc.Y) * (tRef.Y - tc.Y))); + float newSecLen = MathF.Sqrt( + ((tSec.X - tc.X) * (tSec.X - tc.X)) + ((tSec.Y - tc.Y) * (tSec.Y - tc.Y))); + float newRatio = newRefLen > 0f ? newSecLen / newRefLen : this.AxisRatio; + + return new EllipticGradientBrush(tc, tRef, newRatio, this.RepetitionMode, this.ColorStopsArray); } /// public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) => - new RadialGradientBrushApplicator( + new EllipticGradientBrushApplicator( configuration, options, - source, - this.center, - this.referenceAxisEnd, - this.axisRatio, - this.ColorStops, + targetRegion, + this, + this.ColorStopsArray, this.RepetitionMode); /// - private sealed class RadialGradientBrushApplicator : GradientBrushApplicator + private sealed class EllipticGradientBrushApplicator : GradientBrushApplicator where TPixel : unmanaged, IPixel { private readonly PointF center; - private readonly PointF referenceAxisEnd; - - private readonly float axisRatio; - - private readonly double rotation; - - private readonly float referenceRadius; - - private readonly float secondRadius; - private readonly float cosRotation; private readonly float sinRotation; - private readonly float secondRadiusSquared; - private readonly float referenceRadiusSquared; + private readonly float secondRadiusSquared; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration instance to use when performing operations. /// The graphics options. - /// The target image. - /// Center of the ellipse. - /// Point on one angular points of the ellipse. - /// - /// Ratio of the axis length's. Used to determine the length of the second axis, - /// the first is defined by and . + /// The destination pixel region. + /// The elliptic gradient brush. /// Definition of colors. /// Defines how the gradient colors are repeated. - public RadialGradientBrushApplicator( + public EllipticGradientBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame target, - PointF center, - PointF referenceAxisEnd, - float axisRatio, + Buffer2DRegion targetRegion, + EllipticGradientBrush brush, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) - : base(configuration, options, target, colorStops, repetitionMode) + : base(configuration, options, targetRegion, colorStops, repetitionMode) { - this.center = center; - this.referenceAxisEnd = referenceAxisEnd; - this.axisRatio = axisRatio; - this.rotation = AngleBetween( - this.center, - new PointF(this.center.X + 1, this.center.Y), - this.referenceAxisEnd); - this.referenceRadius = DistanceBetween(this.center, this.referenceAxisEnd); - this.secondRadius = this.referenceRadius * this.axisRatio; - - this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius; - this.secondRadiusSquared = this.secondRadius * this.secondRadius; - - this.sinRotation = (float)Math.Sin(this.rotation); - this.cosRotation = (float)Math.Cos(this.rotation); + this.center = brush.Center; + + float refDx = brush.ReferenceAxisEnd.X - brush.Center.X; + float refDy = brush.ReferenceAxisEnd.Y - brush.Center.Y; + float rotation = MathF.Atan2(refDy, refDx); + float referenceRadius = MathF.Sqrt((refDx * refDx) + (refDy * refDy)); + float secondRadius = referenceRadius * brush.AxisRatio; + + this.referenceRadiusSquared = referenceRadius * referenceRadius; + this.secondRadiusSquared = secondRadius * secondRadius; + this.sinRotation = MathF.Sin(rotation); + this.cosRotation = MathF.Cos(rotation); } /// @@ -135,16 +152,7 @@ protected override float PositionOnGradient(float x, float y) float xSquared = xR * xR; float ySquared = yR * yR; - return (xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared); + return MathF.Sqrt((xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared)); } - - private static float AngleBetween(PointF junction, PointF a, PointF b) - { - PointF vA = a - junction; - PointF vB = b - junction; - return MathF.Atan2(vB.Y, vB.X) - MathF.Atan2(vA.Y, vA.X); - } - - private static float DistanceBetween(PointF p1, PointF p2) => Vector2.Distance(p1, p2); } } diff --git a/src/ImageSharp.Drawing/Processing/Extensions/ClearExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/ClearExtensions.cs deleted file mode 100644 index f60e0f211..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/ClearExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the flood filling of images without blending. -/// -public static class ClearExtensions -{ - /// - /// Flood fills the image with the specified color without any blending. - /// - /// The source image processing context. - /// The color. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear(this IImageProcessingContext source, Color color) - => source.Clear(new SolidBrush(color)); - - /// - /// Flood fills the image with the specified color without any blending. - /// - /// The source image processing context. - /// The drawing options. - /// The color. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear(this IImageProcessingContext source, DrawingOptions options, Color color) - => source.Clear(options, new SolidBrush(color)); - - /// - /// Flood fills the image with the specified brush without any blending. - /// - /// The source image processing context. - /// The brush. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear(this IImageProcessingContext source, Brush brush) => - source.Clear(source.GetDrawingOptions(), brush); - - /// - /// Flood fills the image with the specified brush without any blending. - /// - /// The source image processing context. - /// The drawing options. - /// The brush. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear(this IImageProcessingContext source, DrawingOptions options, Brush brush) - => source.Fill(options.CloneForClearOperation(), brush); - - /// - /// Clones the path graphic options and applies changes required to force clearing. - /// - /// The drawing options to clone - /// A clone of shapeOptions with ColorBlendingMode, AlphaCompositionMode, and BlendPercentage set - internal static DrawingOptions CloneForClearOperation(this DrawingOptions drawingOptions) - { - GraphicsOptions options = drawingOptions.GraphicsOptions.DeepClone(); - options.ColorBlendingMode = PixelColorBlendingMode.Normal; - options.AlphaCompositionMode = PixelAlphaCompositionMode.Src; - options.BlendPercentage = 1F; - - return new DrawingOptions(options, drawingOptions.ShapeOptions, drawingOptions.Transform); - } -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/ClearPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/ClearPathExtensions.cs deleted file mode 100644 index f5ea9a1f9..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/ClearPathExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the flood filling of polygon outlines without blending. -/// -public static class ClearPathExtensions -{ - /// - /// Flood fills the image within the provided region defined by an using the specified - /// color without any blending. - /// - /// The source image processing context. - /// The color. - /// The defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear( - this IImageProcessingContext source, - Color color, - IPath region) - => source.Clear(new SolidBrush(color), region); - - /// - /// Flood fills the image within the provided region defined by an using the specified color - /// without any blending. - /// - /// The source image processing context. - /// The drawing options. - /// The color. - /// The defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - IPath region) - => source.Clear(options, new SolidBrush(color), region); - - /// - /// Flood fills the image within the provided region defined by an using the specified brush - /// without any blending. - /// - /// The source image processing context. - /// The brush. - /// The defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear( - this IImageProcessingContext source, - Brush brush, - IPath region) - => source.Clear(source.GetDrawingOptions(), brush, region); - - /// - /// Flood fills the image within the provided region defined by an using the specified brush - /// without any blending. - /// - /// The source image processing context. - /// The drawing options. - /// The brush. - /// The defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - IPath region) - => source.Fill(options.CloneForClearOperation(), brush, region); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/ClearRectangleExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/ClearRectangleExtensions.cs deleted file mode 100644 index 0654942ac..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/ClearRectangleExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the flood filling of rectangle outlines without blending. -/// -public static class ClearRectangleExtensions -{ - /// - /// Flood fills the image in the rectangle of the provided rectangle with the specified color without any blending. - /// - /// The source image processing context. - /// The color. - /// The rectangle defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear(this IImageProcessingContext source, Color color, RectangleF rectangle) - => source.Clear(new SolidBrush(color), rectangle); - - /// - /// Flood fills the image in the rectangle of the provided rectangle with the specified color without any blending. - /// - /// The source image processing context. - /// The drawing options. - /// The color. - /// The rectangle defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - RectangleF rectangle) - => source.Clear(options, new SolidBrush(color), rectangle); - - /// - /// Flood fills the image in the rectangle of the provided rectangle with the specified brush without any blending. - /// - /// The source image processing context. - /// The brush. - /// The rectangle defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear( - this IImageProcessingContext source, - Brush brush, - RectangleF rectangle) - => source.Clear(brush, new RectangularPolygon(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height)); - - /// - /// Flood fills the image at the given rectangle bounds with the specified brush without any blending. - /// - /// The source image processing context. - /// The drawing options. - /// The brush. - /// The rectangle defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Clear( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - RectangleF rectangle) - => source.Clear(options, brush, new RectangularPolygon(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height)); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/ClipPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/ClipPathExtensions.cs deleted file mode 100644 index 10ac9ba3a..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/ClipPathExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the application of processors within a clipped path. -/// -public static class ClipPathExtensions -{ - /// - /// Applies the processing operation within the region defined by an . - /// - /// The source image processing context. - /// - /// The defining the clip region. Only pixels inside the clip are affected. - /// - /// - /// The operation to perform. This executes in the clipped context so results are constrained to the - /// clip bounds. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Clip( - this IImageProcessingContext source, - IPath region, - Action operation) - => source.ApplyProcessor(new ClipPathProcessor(source.GetDrawingOptions(), region, operation)); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs deleted file mode 100644 index 707e53ac1..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawBezierExtensions.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the drawing of Bezier paths. -/// -public static class DrawBezierExtensions -{ - /// - /// Draws the provided points as an open Bezier path with the supplied pen - /// - /// The source image processing context. - /// The options. - /// The pen. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - DrawingOptions options, - Pen pen, - params PointF[] points) => - source.Draw(options, pen, new Path(new CubicBezierLineSegment(points))); - - /// - /// Draws the provided points as an open Bezier path with the supplied pen - /// - /// The source image processing context. - /// The pen. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - Pen pen, - params PointF[] points) => - source.Draw(pen, new Path(new CubicBezierLineSegment(points))); - - /// - /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The source image processing context. - /// The options. - /// The brush. - /// The thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - float thickness, - params PointF[] points) => - source.Draw(options, new SolidPen(brush, thickness), new Path(new CubicBezierLineSegment(points))); - - /// - /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The source image processing context. - /// The brush. - /// The thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - Brush brush, - float thickness, - params PointF[] points) => - source.Draw(new SolidPen(brush, thickness), new Path(new CubicBezierLineSegment(points))); - - /// - /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The source image processing context. - /// The color. - /// The thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - Color color, - float thickness, - params PointF[] points) => - source.DrawBeziers(new SolidBrush(color), thickness, points); - - /// - /// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The source image processing context. - /// The options. - /// The color. - /// The thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawBeziers( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - float thickness, - params PointF[] points) => - source.DrawBeziers(options, new SolidBrush(color), thickness, points); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs deleted file mode 100644 index d4fb0bef3..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawLineExtensions.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the drawing of lines. -/// -public static class DrawLineExtensions -{ - /// - /// Draws the provided points as an open linear path at the provided thickness with the supplied brush. - /// - /// The source image processing context. - /// The options. - /// The brush. - /// The line thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawLine( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - float thickness, - params PointF[] points) => - source.Draw(options, new SolidPen(brush, thickness), new Path(points)); - - /// - /// Draws the provided points as an open linear path at the provided thickness with the supplied brush. - /// - /// The source image processing context. - /// The brush. - /// The line thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawLine( - this IImageProcessingContext source, - Brush brush, - float thickness, - params PointF[] points) => - source.Draw(new SolidPen(brush, thickness), new Path(points)); - - /// - /// Draws the provided points as an open linear path at the provided thickness with the supplied brush. - /// - /// The source image processing context. - /// The color. - /// The line thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawLine( - this IImageProcessingContext source, - Color color, - float thickness, - params PointF[] points) => - source.DrawLine(new SolidBrush(color), thickness, points); - - /// - /// Draws the provided points as an open linear path at the provided thickness with the supplied brush. - /// - /// The source image processing context. - /// The options. - /// The color. - /// The line thickness. - /// The points. - /// The to allow chaining of operations.> - public static IImageProcessingContext DrawLine( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - float thickness, - params PointF[] points) => - source.DrawLine(options, new SolidBrush(color), thickness, points); - - /// - /// Draws the provided points as an open linear path with the supplied pen. - /// - /// The source image processing context. - /// The options. - /// The pen. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawLine( - this IImageProcessingContext source, - DrawingOptions options, - Pen pen, - params PointF[] points) => - source.Draw(options, pen, new Path(points)); - - /// - /// Draws the provided points as an open linear path with the supplied pen. - /// - /// The source image processing context. - /// The pen. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawLine( - this IImageProcessingContext source, - Pen pen, - params PointF[] points) => - source.Draw(pen, new Path(points)); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs deleted file mode 100644 index 6726e2bfc..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the drawing of collections of polygon outlines. -/// -public static class DrawPathCollectionExtensions -{ - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The source image processing context. - /// The options. - /// The pen. - /// The paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Pen pen, - IPathCollection paths) - { - foreach (IPath path in paths) - { - source.Draw(options, pen, path); - } - - return source; - } - - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The source image processing context. - /// The pen. - /// The paths. - /// The to allow chaining of operations. - public static IImageProcessingContext - Draw(this IImageProcessingContext source, Pen pen, IPathCollection paths) - => source.Draw(source.GetDrawingOptions(), pen, paths); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The options. - /// The brush. - /// The thickness. - /// The shapes. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - float thickness, - IPathCollection paths) => - source.Draw(options, new SolidPen(brush, thickness), paths); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The brush. - /// The thickness. - /// The paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Brush brush, - float thickness, - IPathCollection paths) => - source.Draw(new SolidPen(brush, thickness), paths); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The options. - /// The color. - /// The thickness. - /// The paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - float thickness, - IPathCollection paths) => - source.Draw(options, new SolidBrush(color), thickness, paths); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The color. - /// The thickness. - /// The paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Color color, - float thickness, - IPathCollection paths) => - source.Draw(new SolidBrush(color), thickness, paths); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs deleted file mode 100644 index fd0ed2aa3..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the drawing of polygon outlines. -/// -public static class DrawPathExtensions -{ - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The source image processing context. - /// The options. - /// The pen. - /// The path. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Pen pen, - IPath path) => - source.ApplyProcessor(new DrawPathProcessor(options, pen, path)); - - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The source image processing context. - /// The pen. - /// The path. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw(this IImageProcessingContext source, Pen pen, IPath path) => - source.Draw(source.GetDrawingOptions(), pen, path); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The options. - /// The brush. - /// The thickness. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - float thickness, - IPath path) => - source.Draw(options, new SolidPen(brush, thickness), path); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The brush. - /// The thickness. - /// The path. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Brush brush, - float thickness, - IPath path) => - source.Draw(new SolidPen(brush, thickness), path); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The options. - /// The color. - /// The thickness. - /// The path. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - float thickness, - IPath path) => - source.Draw(options, new SolidBrush(color), thickness, path); - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The color. - /// The thickness. - /// The path. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Color color, - float thickness, - IPath path) => - source.Draw(new SolidBrush(color), thickness, path); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs deleted file mode 100644 index 6ebad1e06..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the drawing of closed linear polygons. -/// -public static class DrawPolygonExtensions -{ - /// - /// Draws the provided points as a closed linear polygon with the provided pen. - /// - /// The source image processing context. - /// The pen. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - Pen pen, - params PointF[] points) => - source.Draw(source.GetDrawingOptions(), pen, new Polygon(points)); - - /// - /// Draws the provided points as a closed linear polygon with the provided pen. - /// - /// The source image processing context. - /// The options. - /// The pen. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - DrawingOptions options, - Pen pen, - params PointF[] points) => - source.Draw(options, pen, new Polygon(points)); - - /// - /// Draws the provided points as a closed linear polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The options. - /// The brush. - /// The thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - float thickness, - params PointF[] points) => - source.DrawPolygon(options, new SolidPen(brush, thickness), points); - - /// - /// Draws the provided points as a closed linear polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The brush. - /// The thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - Brush brush, - float thickness, - params PointF[] points) => - source.DrawPolygon(new SolidPen(brush, thickness), points); - - /// - /// Draws the provided points as a closed linear polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The color. - /// The thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - Color color, - float thickness, - params PointF[] points) => - source.DrawPolygon(new SolidBrush(color), thickness, points); - - /// - /// Draws the provided points as a closed linear polygon with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The options. - /// The color. - /// The thickness. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawPolygon( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - float thickness, - params PointF[] points) => - source.DrawPolygon(options, new SolidBrush(color), thickness, points); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs deleted file mode 100644 index 0971db357..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the drawing of rectangles. -/// -public static class DrawRectangleExtensions -{ - /// - /// Draws the outline of the rectangle with the provided pen. - /// - /// The source image processing context. - /// The options. - /// The pen. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Pen pen, - RectangleF shape) => - source.Draw(options, pen, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); - - /// - /// Draws the outline of the rectangle with the provided pen. - /// - /// The source image processing context. - /// The pen. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw(this IImageProcessingContext source, Pen pen, RectangleF shape) => - source.Draw(source.GetDrawingOptions(), pen, shape); - - /// - /// Draws the outline of the rectangle with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The options. - /// The brush. - /// The thickness. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - float thickness, - RectangleF shape) => - source.Draw(options, new SolidPen(brush, thickness), shape); - - /// - /// Draws the outline of the rectangle with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The brush. - /// The thickness. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Brush brush, - float thickness, - RectangleF shape) => - source.Draw(new SolidPen(brush, thickness), shape); - - /// - /// Draws the outline of the rectangle with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The options. - /// The color. - /// The thickness. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - float thickness, - RectangleF shape) => - source.Draw(options, new SolidBrush(color), thickness, shape); - - /// - /// Draws the outline of the rectangle with the provided brush at the provided thickness. - /// - /// The source image processing context. - /// The color. - /// The thickness. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Draw( - this IImageProcessingContext source, - Color color, - float thickness, - RectangleF shape) => - source.Draw(new SolidBrush(color), thickness, shape); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs deleted file mode 100644 index 9ad68315a..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.Fonts; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Text; - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the drawing of text. -/// -public static class DrawTextExtensions -{ - /// - /// Draws the text onto the image filled with the given color. - /// - /// The source image processing context. - /// The text to draw. - /// The font. - /// The color. - /// The location. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - string text, - Font font, - Color color, - PointF location) => - source.DrawText(source.GetDrawingOptions(), text, font, color, location); - - /// - /// Draws the text using the supplied drawing options onto the image filled with the given color. - /// - /// The source image processing context. - /// The drawing options. - /// The text to draw. - /// The font. - /// The color. - /// The location. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - DrawingOptions drawingOptions, - string text, - Font font, - Color color, - PointF location) => - source.DrawText(drawingOptions, text, font, Brushes.Solid(color), null, location); - - /// - /// Draws the text using the supplied text options onto the image filled via the brush. - /// - /// The source image processing context. - /// The text rendering options. - /// The text to draw. - /// The color. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - RichTextOptions textOptions, - string text, - Color color) => - source.DrawText(textOptions, text, Brushes.Solid(color), null); - - /// - /// Draws the text onto the image filled via the brush. - /// - /// The source image processing context. - /// The text to draw. - /// The font. - /// The brush used to fill the text. - /// The location. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - string text, - Font font, - Brush brush, - PointF location) => - source.DrawText(source.GetDrawingOptions(), text, font, brush, location); - - /// - /// Draws the text onto the image outlined via the pen. - /// - /// The source image processing context. - /// The text to draw. - /// The font. - /// The pen used to outline the text. - /// The location. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - string text, - Font font, - Pen pen, - PointF location) => - source.DrawText(source.GetDrawingOptions(), text, font, pen, location); - - /// - /// Draws the text onto the image filled via the brush then outlined via the pen. - /// - /// The source image processing context. - /// The text to draw. - /// The font. - /// The brush used to fill the text. - /// The pen used to outline the text. - /// The location. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - string text, - Font font, - Brush brush, - Pen pen, - PointF location) - { - RichTextOptions textOptions = new(font) { Origin = location }; - return source.DrawText(textOptions, text, brush, pen); - } - - /// - /// Draws the text using the given options onto the image filled via the brush. - /// - /// The source image processing context. - /// The text rendering options. - /// The text to draw. - /// The brush used to fill the text. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - RichTextOptions textOptions, - string text, - Brush brush) => - source.DrawText(source.GetDrawingOptions(), textOptions, text, brush, null); - - /// - /// Draws the text using the given options onto the image outlined via the pen. - /// - /// The source image processing context. - /// The text rendering options. - /// The text to draw. - /// The pen used to outline the text. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - RichTextOptions textOptions, - string text, - Pen pen) => - source.DrawText(source.GetDrawingOptions(), textOptions, text, null, pen); - - /// - /// Draws the text using the given options onto the image filled via the brush then outlined via the pen. - /// - /// The source image processing context. - /// The text rendering options. - /// The text to draw. - /// The brush used to fill the text. - /// The pen used to outline the text. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - RichTextOptions textOptions, - string text, - Brush? brush, - Pen? pen) => - source.DrawText(source.GetDrawingOptions(), textOptions, text, brush, pen); - - /// - /// Draws the text onto the image outlined via the pen. - /// - /// The source image processing context. - /// The drawing options. - /// The text to draw. - /// The font. - /// The pen used to outline the text. - /// The location. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - DrawingOptions drawingOptions, - string text, - Font font, - Pen pen, - PointF location) - => source.DrawText(drawingOptions, text, font, null, pen, location); - - /// - /// Draws the text onto the image filled via the brush. - /// - /// The source image processing context. - /// The drawing options. - /// The text to draw. - /// The font. - /// The brush used to fill the text. - /// The location. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - DrawingOptions drawingOptions, - string text, - Font font, - Brush brush, - PointF location) - => source.DrawText(drawingOptions, text, font, brush, null, location); - - /// - /// Draws the text using the given drawing options onto the image filled via the brush then outlined via the pen. - /// - /// The source image processing context. - /// The drawing options. - /// The text to draw. - /// The font. - /// The brush used to fill the text. - /// The pen used to outline the text. - /// The location. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - DrawingOptions drawingOptions, - string text, - Font font, - Brush? brush, - Pen? pen, - PointF location) - { - RichTextOptions textOptions = new(font) { Origin = location }; - return source.ApplyProcessor(new DrawTextProcessor(drawingOptions, textOptions, text, brush, pen)); - } - - /// - /// Draws the text using the given options onto the image filled via the brush then outlined via the pen. - /// - /// The source image processing context. - /// The drawing options. - /// The text rendering options. - /// The text to draw. - /// The brush used to fill the text. - /// The pen used to outline the text. - /// The to allow chaining of operations. - public static IImageProcessingContext DrawText( - this IImageProcessingContext source, - DrawingOptions drawingOptions, - RichTextOptions textOptions, - string text, - Brush? brush, - Pen? pen) - => source.ApplyProcessor(new DrawTextProcessor(drawingOptions, textOptions, text, brush, pen)); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillExtensions.cs deleted file mode 100644 index 86bb20c23..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the flood filling of images. -/// -public static class FillExtensions -{ - /// - /// Flood fills the image with the specified color. - /// - /// The source image processing context. - /// The color. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color) - => source.Fill(new SolidBrush(color)); - - /// - /// Flood fills the image with the specified color. - /// - /// The source image processing context. - /// The drawing options. - /// The color. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill(this IImageProcessingContext source, DrawingOptions options, Color color) - => source.Fill(options, new SolidBrush(color)); - - /// - /// Flood fills the image with the specified brush. - /// - /// The source image processing context. - /// The brush. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill(this IImageProcessingContext source, Brush brush) - => source.Fill(source.GetDrawingOptions(), brush); - - /// - /// Flood fills the image with the specified brush. - /// - /// The source image processing context. - /// The drawing options. - /// The brush. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill(this IImageProcessingContext source, DrawingOptions options, Brush brush) - => source.ApplyProcessor(new FillProcessor(options, brush)); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs deleted file mode 100644 index 1629bdfa8..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the flood filling of polygon outlines. -/// -public static class FillPathBuilderExtensions -{ - /// - /// Flood fills the image within the provided region defined by an method - /// using the specified color. - /// - /// The source image processing context. - /// The color. - /// The method defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Color color, - Action region) - => source.Fill(new SolidBrush(color), region); - - /// - /// Flood fills the image within the provided region defined by an method - /// using the specified color. - /// - /// The source image processing context. - /// The drawing options. - /// The color. - /// The method defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - Action region) - => source.Fill(options, new SolidBrush(color), region); - - /// - /// Flood fills the image within the provided region defined by an method - /// using the specified brush. - /// - /// The source image processing context. - /// The brush. - /// The method defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Brush brush, - Action region) - => source.Fill(source.GetDrawingOptions(), brush, region); - - /// - /// Flood fills the image within the provided region defined by an method - /// using the specified brush. - /// - /// The source image processing context. - /// The graphics options. - /// The brush. - /// The method defining the region to fill. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - Action region) - { - PathBuilder pb = new(); - region(pb); - - return source.Fill(options, brush, pb.Build()); - } -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs deleted file mode 100644 index 3b8cb4d8b..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Text; - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the filling of collections of polygon outlines. -/// -public static class FillPathCollectionExtensions -{ - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The source image processing context. - /// The graphics options. - /// The brush. - /// The collection of paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - IPathCollection paths) - { - foreach (IPath s in paths) - { - source.Fill(options, brush, s); - } - - return source; - } - - /// - /// Flood fills the image in the shape of the provided glyphs with the specified brush and pen. - /// For multi-layer glyphs, a heuristic is used to decide whether to fill or stroke each layer. - /// - /// The source image processing context. - /// The graphics options. - /// The brush. - /// The pen. - /// The collection of glyph paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - Pen pen, - IReadOnlyList paths) - => source.Fill(options, brush, pen, paths, static (gp, layer, path) => - { - if (layer.Kind == GlyphLayerKind.Decoration) - { - // Decorations (underlines, strikethroughs, etc) are always filled. - return true; - } - - if (layer.Kind == GlyphLayerKind.Glyph) - { - // Standard glyph layers are filled by default. - return true; - } - - // Default heuristic: stroke "background-like" layers (large coverage), fill others. - // Use the bounding box area as an approximation of the glyph area as it is cheaper to compute. - float glyphArea = gp.Bounds.Width * gp.Bounds.Height; - float layerArea = path.ComputeArea(); - - if (layerArea <= 0 || glyphArea <= 0) - { - return false; // degenerate glyph, don't fill - } - - float coverage = layerArea / glyphArea; - - // <50% coverage, fill. Otherwise, stroke. - return coverage < 0.50F; - }); - - /// - /// Flood fills the image in the shape of the provided glyphs with the specified brush and pen. - /// For multi-layer glyphs, a heuristic is used to decide whether to fill or stroke each layer. - /// - /// The source image processing context. - /// The graphics options. - /// The brush. - /// The pen. - /// The collection of glyph paths. - /// - /// A function that decides whether to fill or stroke a given layer within a multi-layer (painted) glyph. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - Pen pen, - IReadOnlyList paths, - Func shouldFillLayer) - { - foreach (GlyphPathCollection gp in paths) - { - if (gp.LayerCount == 0) - { - continue; - } - - if (gp.LayerCount == 1) - { - // Single-layer glyph: just fill with the supplied brush. - source.Fill(options, brush, gp.Paths); - continue; - } - - // Multi-layer: decide per layer whether to fill or stroke. - for (int i = 0; i < gp.Layers.Count; i++) - { - GlyphLayerInfo layer = gp.Layers[i]; - IPath path = gp.PathList[i]; - - if (shouldFillLayer(gp, layer, path)) - { - // Respect the layer's fill rule if different to the drawing options. - DrawingOptions o = options.CloneOrReturnForRules( - layer.IntersectionRule, - layer.PixelAlphaCompositionMode, - layer.PixelColorBlendingMode); - - source.Fill(o, brush, path); - } - else - { - // Outline only to preserve interior detail. - source.Draw(options, pen, path); - } - } - } - - return source; - } - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The source image processing context. - /// The brush. - /// The collection of paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Brush brush, - IPathCollection paths) => - source.Fill(source.GetDrawingOptions(), brush, paths); - - /// - /// Flood fills the image in the shape of the provided glyphs with the specified brush and pen. - /// For multi-layer glyphs, a heuristic is used to decide whether to fill or stroke each layer. - /// - /// The source image processing context. - /// The brush. - /// The pen. - /// The collection of glyph paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Brush brush, - Pen pen, - IReadOnlyList paths) => - source.Fill(source.GetDrawingOptions(), brush, pen, paths); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified color. - /// - /// The source image processing context. - /// The options. - /// The color. - /// The paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - IPathCollection paths) => - source.Fill(options, new SolidBrush(color), paths); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified color. - /// - /// The source image processing context. - /// The color. - /// The collection of paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Color color, - IPathCollection paths) => - source.Fill(new SolidBrush(color), paths); - - /// - /// Flood fills the image in the shape of the provided glyphs with the specified color. - /// For multi-layer glyphs, a heuristic is used to decide whether to fill or stroke each layer. - /// - /// The source image processing context. - /// The color. - /// The collection of glyph paths. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Color color, - IReadOnlyList paths) => - source.Fill(new SolidBrush(color), new SolidPen(color), paths); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs deleted file mode 100644 index ef26eb107..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the filling of polygon outlines. -/// -public static class FillPathExtensions -{ - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The source image processing context. - /// The color. - /// The logic path. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Color color, - IPath path) => - source.Fill(new SolidBrush(color), path); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The source image processing context. - /// The drawing options. - /// The color. - /// The logic path. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - IPath path) => - source.Fill(options, new SolidBrush(color), path); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The source image processing context. - /// The brush. - /// The logic path. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - Brush brush, - IPath path) => - source.Fill(source.GetDrawingOptions(), brush, path); - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The source image processing context. - /// The drawing options. - /// The brush. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - IPath path) => - source.ApplyProcessor(new FillPathProcessor(options, brush, path)); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs deleted file mode 100644 index 6c87e509e..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillPolygonExtensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the filling of closed linear polygons. -/// -public static class FillPolygonExtensions -{ - /// - /// Flood fills the image in the shape of a linear polygon described by the points - /// - /// The source image processing context. - /// The options. - /// The brush. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext FillPolygon( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - params PointF[] points) => - source.Fill(options, brush, new Polygon(points)); - - /// - /// Flood fills the image in the shape of a linear polygon described by the points - /// - /// The source image processing context. - /// The brush. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext FillPolygon( - this IImageProcessingContext source, - Brush brush, - params PointF[] points) => - source.Fill(brush, new Polygon(points)); - - /// - /// Flood fills the image in the shape of a linear polygon described by the points - /// - /// The source image processing context. - /// The options. - /// The color. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext FillPolygon( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - params PointF[] points) => - source.Fill(options, new SolidBrush(color), new Polygon(points)); - - /// - /// Flood fills the image in the shape of a linear polygon described by the points - /// - /// The source image processing context. - /// The color. - /// The points. - /// The to allow chaining of operations. - public static IImageProcessingContext FillPolygon( - this IImageProcessingContext source, - Color color, - params PointF[] points) => - source.Fill(new SolidBrush(color), new Polygon(points)); -} diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs deleted file mode 100644 index f8d8c7632..000000000 --- a/src/ImageSharp.Drawing/Processing/Extensions/FillRectangleExtensions.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Processing; - -/// -/// Adds extensions that allow the filling of rectangles. -/// -public static class FillRectangleExtensions -{ - /// - /// Flood fills the image in the shape of the provided rectangle with the specified brush. - /// - /// The source image processing context. - /// The options. - /// The brush. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Brush brush, - RectangleF shape) => - source.Fill(options, brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); - - /// - /// Flood fills the image in the shape of the provided rectangle with the specified brush. - /// - /// The source image processing context. - /// The brush. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext - Fill(this IImageProcessingContext source, Brush brush, RectangleF shape) => - source.Fill(brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); - - /// - /// Flood fills the image in the shape of the provided rectangle with the specified brush. - /// - /// The source image processing context. - /// The options. - /// The color. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext Fill( - this IImageProcessingContext source, - DrawingOptions options, - Color color, - RectangleF shape) => - source.Fill(options, new SolidBrush(color), shape); - - /// - /// Flood fills the image in the shape of the provided rectangle with the specified brush. - /// - /// The source image processing context. - /// The color. - /// The shape. - /// The to allow chaining of operations. - public static IImageProcessingContext - Fill(this IImageProcessingContext source, Color color, RectangleF shape) => - source.Fill(new SolidBrush(color), shape); -} diff --git a/src/ImageSharp.Drawing/Processing/GradientBrush.cs b/src/ImageSharp.Drawing/Processing/GradientBrush.cs index 065125c6a..fddda4b2a 100644 --- a/src/ImageSharp.Drawing/Processing/GradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/GradientBrush.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using SixLabors.ImageSharp.Drawing.Utilities; +using SixLabors.ImageSharp.Drawing.Helpers; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Drawing.Processing; @@ -18,18 +18,25 @@ public abstract class GradientBrush : Brush protected GradientBrush(GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) { this.RepetitionMode = repetitionMode; - this.ColorStops = colorStops; + + InsertionSort(colorStops, (a, b) => a.Ratio.CompareTo(b.Ratio)); + this.ColorStopsArray = colorStops; } /// /// Gets how the colors are repeated beyond the interval [0..1]. /// - protected GradientRepetitionMode RepetitionMode { get; } + public GradientRepetitionMode RepetitionMode { get; } + + /// + /// Gets the color stops for this gradient. + /// + public ReadOnlySpan ColorStops => this.ColorStopsArray; /// - /// Gets the list of color stops for this gradient. + /// Gets the color stops array for use by derived applicators. /// - protected ColorStop[] ColorStops { get; } + protected ColorStop[] ColorStopsArray { get; } /// public override bool Equals(Brush? other) @@ -37,7 +44,7 @@ public override bool Equals(Brush? other) if (other is GradientBrush brush) { return this.RepetitionMode == brush.RepetitionMode - && this.ColorStops?.SequenceEqual(brush.ColorStops) == true; + && this.ColorStopsArray?.SequenceEqual(brush.ColorStopsArray) == true; } return false; @@ -45,7 +52,29 @@ public override bool Equals(Brush? other) /// public override int GetHashCode() - => HashCode.Combine(this.RepetitionMode, this.ColorStops); + => HashCode.Combine(this.RepetitionMode, this.ColorStopsArray); + + /// + /// Sorts the collection in place using a stable insertion sort. + /// is not stable and can reorder + /// equal-ratio color stops, producing non-deterministic gradient results. + /// + private static void InsertionSort(T[] collection, Comparison comparison) + { + int count = collection.Length; + for (int j = 1; j < count; j++) + { + T key = collection[j]; + + int i = j - 1; + for (; i >= 0 && comparison(collection[i], key) > 0; i--) + { + collection[i + 1] = collection[i]; + } + + collection[i + 1] = key; + } + } /// /// Base class for gradient brush applicators @@ -73,23 +102,20 @@ internal abstract class GradientBrushApplicator : BrushApplicator /// The configuration instance to use when performing operations. /// The graphics options. - /// The target image. + /// The destination pixel region. /// An array of color stops sorted by their position. /// Defines if and how the gradient should be repeated. protected GradientBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame target, + Buffer2DRegion targetRegion, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) - : base(configuration, options, target) + : base(configuration, options, targetRegion) { this.colorStops = colorStops; - - // Ensure the color-stop order is correct. - InsertionSort(this.colorStops, (x, y) => x.Ratio.CompareTo(y.Ratio)); this.repetitionMode = repetitionMode; - this.scanlineWidth = target.Width; + this.scanlineWidth = targetRegion.Width; this.allocator = configuration.MemoryAllocator; this.blenderBuffers = new ThreadLocalBlenderBuffers(this.allocator, this.scanlineWidth); } @@ -98,7 +124,10 @@ protected GradientBrushApplicator( { get { - float positionOnCompleteGradient = this.PositionOnGradient(x + 0.5f, y + 0.5f); + float fx = x + 0.5f; + float fy = y + 0.5f; + + float positionOnCompleteGradient = this.PositionOnGradient(fx, fy); switch (this.repetitionMode) { @@ -170,7 +199,9 @@ public override void Apply(Span scanline, int x, int y) } } - Span destinationRow = this.Target.PixelBuffer.DangerousGetRowSpan(y).Slice(x, scanline.Length); + int localY = y - this.TargetRegion.Rectangle.Y; + int localX = x - this.TargetRegion.Rectangle.X; + Span destinationRow = this.TargetRegion.DangerousGetRowSpan(localY).Slice(localX, scanline.Length); this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlays, amounts); } @@ -227,29 +258,5 @@ protected override void Dispose(bool disposing) return (localGradientFrom, localGradientTo); } - - /// - /// Provides a stable sorting algorithm for the given array. - /// is not stable. - /// - /// The type of element to sort. - /// The array to sort. - /// The comparison delegate. - private static void InsertionSort(T[] collection, Comparison comparison) - { - int count = collection.Length; - for (int j = 1; j < count; j++) - { - T key = collection[j]; - - int i = j - 1; - for (; i >= 0 && comparison(collection[i], key) > 0; i--) - { - collection[i + 1] = collection[i]; - } - - collection[i + 1] = key; - } - } } } diff --git a/src/ImageSharp.Drawing/Processing/IDrawingCanvas.cs b/src/ImageSharp.Drawing/Processing/IDrawingCanvas.cs new file mode 100644 index 000000000..bd9fb9f43 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/IDrawingCanvas.cs @@ -0,0 +1,443 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Text; +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Drawing.Processing; + +/// +/// Represents a drawing canvas over a frame target. +/// +public interface IDrawingCanvas : IDisposable +{ + /// + /// Gets the local bounds of this canvas. + /// + public Rectangle Bounds { get; } + + /// + /// Gets the number of saved states currently on the canvas stack. + /// + public int SaveCount { get; } + + /// + /// Saves the current drawing state on the state stack. + /// + /// + /// This operation stores the current canvas state by reference. + /// If the same instance is mutated after + /// , those mutations are visible when restoring. + /// + /// The save count after the state has been pushed. + public int Save(); + + /// + /// Saves the current drawing state and replaces the active state with the provided options and clip paths. + /// + /// + /// The provided instance is stored by reference. + /// Mutating it after this call mutates the active/restored state behavior. + /// + /// Drawing options for the new active state. + /// Clip paths for the new active state. + /// The save count after the previous state has been pushed. + public int Save(DrawingOptions options, params IPath[] clipPaths); + + /// + /// Saves the current drawing state and begins an isolated compositing layer. + /// Subsequent draw commands render to a temporary surface. When + /// is called, the layer is composited back onto the parent using the default + /// . + /// + /// The save count after the layer state has been pushed. + public int SaveLayer(); + + /// + /// Saves the current drawing state and begins an isolated compositing layer. + /// Subsequent draw commands render to a temporary surface. When + /// is called, the layer is composited back onto the parent using the specified + /// (blend mode, alpha composition, opacity). + /// + /// + /// Graphics options controlling how the layer is composited on restore. + /// + /// The save count after the layer state has been pushed. + public int SaveLayer(GraphicsOptions layerOptions); + + /// + /// Saves the current drawing state and begins an isolated compositing layer + /// bounded to a subregion. Subsequent draw commands render to a temporary surface + /// sized to . When is called, the + /// layer is composited back onto the parent using the specified + /// . + /// + /// + /// Graphics options controlling how the layer is composited on restore. + /// + /// + /// The local bounds of the layer. Only this region is allocated and composited. + /// + /// The save count after the layer state has been pushed. + public int SaveLayer(GraphicsOptions layerOptions, Rectangle bounds); + + /// + /// Restores the most recently saved state. + /// + /// + /// If the most recently saved state was created by a SaveLayer overload, + /// pending commands are flushed to the layer surface, the layer is composited back onto + /// the parent target, and the temporary layer buffer is disposed. + /// + public void Restore(); + + /// + /// Restores to a specific save count. + /// + /// + /// State frames above are discarded, + /// and the last discarded frame becomes the current state. + /// If any discarded state was created by a SaveLayer overload, + /// the layer is composited and its resources disposed. + /// + /// The save count to restore to. + public void RestoreTo(int saveCount); + + /// + /// Creates a child canvas over a subregion in local coordinates. + /// + /// The child region in local coordinates. + /// A child canvas with local origin at (0,0). + public IDrawingCanvas CreateRegion(Rectangle region); + + /// + /// Clears the whole canvas using the given brush and clear-style composition options. + /// + /// Brush used to shade destination pixels during clear. + public void Clear(Brush brush); + + /// + /// Clears a local region using the given brush and clear-style composition options. + /// + /// Brush used to shade destination pixels during clear. + /// Region to clear in local coordinates. + public void Clear(Brush brush, Rectangle region); + + /// + /// Clears a path region using the given brush and clear-style composition options. + /// + /// Brush used to shade destination pixels during clear. + /// The path region to clear. + public void Clear(Brush brush, IPath path); + + /// + /// Fills the whole canvas using the given brush. + /// + /// Brush used to shade destination pixels. + public void Fill(Brush brush); + + /// + /// Fills a local region using the given brush. + /// + /// Brush used to shade destination pixels. + /// Region to fill in local coordinates. + public void Fill(Brush brush, Rectangle region); + + /// + /// Fills all paths in a collection using the given brush and drawing options. + /// + /// Brush used to shade covered pixels. + /// Path collection to fill. + public void Fill(Brush brush, IPathCollection paths); + + /// + /// Fills a path built by the provided builder using the given brush. + /// + /// Brush used to shade covered pixels. + /// The path builder describing the fill region. + public void Fill(Brush brush, PathBuilder pathBuilder); + + /// + /// Fills a path in local coordinates using the given brush. + /// + /// Brush used to shade covered pixels. + /// The path to fill. + public void Fill(Brush brush, IPath path); + + /// + /// Applies an image-processing operation to a local region. + /// + /// The local region to process. + /// The image-processing operation to apply to the region. + public void Process(Rectangle region, Action operation); + + /// + /// Applies an image-processing operation to a region described by a path builder. + /// + /// The path builder describing the region to process. + /// The image-processing operation to apply to the region. + public void Process(PathBuilder pathBuilder, Action operation); + + /// + /// Applies an image-processing operation to a path region. + /// + /// + /// The operation is constrained to the path bounds and then composited back using an . + /// + /// The path region to process. + /// The image-processing operation to apply to the region. + public void Process(IPath path, Action operation); + + /// + /// Draws an arc outline using the provided pen and drawing options. + /// + /// Pen used to generate the arc outline. + /// Arc center point in local coordinates. + /// Arc radii in local coordinates. + /// Ellipse rotation in degrees. + /// Arc start angle in degrees. + /// Arc sweep angle in degrees. + public void DrawArc(Pen pen, PointF center, SizeF radius, float rotation, float startAngle, float sweepAngle); + + /// + /// Draws a cubic bezier outline using the provided pen and drawing options. + /// + /// Pen used to generate the bezier outline. + /// Bezier control points. + public void DrawBezier(Pen pen, params PointF[] points); + + /// + /// Draws an ellipse outline using the provided pen and drawing options. + /// + /// Pen used to generate the ellipse outline. + /// Ellipse center point in local coordinates. + /// Ellipse width and height in local coordinates. + public void DrawEllipse(Pen pen, PointF center, SizeF size); + + /// + /// Draws a polyline outline using the provided pen and drawing options. + /// + /// Pen used to generate the line outline. + /// Polyline points. + public void DrawLine(Pen pen, params PointF[] points); + + /// + /// Draws a rectangular outline using the provided pen and drawing options. + /// + /// Pen used to generate the rectangle outline. + /// Rectangle region to stroke. + public void Draw(Pen pen, Rectangle region); + + /// + /// Draws all paths in a collection using the provided pen and drawing options. + /// + /// Pen used to generate outlines. + /// Path collection to stroke. + public void Draw(Pen pen, IPathCollection paths); + + /// + /// Draws a path outline built by the provided builder using the given pen. + /// + /// Pen used to generate the outline fill path. + /// The path builder describing the path to stroke. + public void Draw(Pen pen, PathBuilder pathBuilder); + + /// + /// Draws a path outline in local coordinates using the given pen. + /// + /// Pen used to generate the outline fill path. + /// The path to stroke. + public void Draw(Pen pen, IPath path); + + /// + /// Draws text onto this canvas. + /// + /// The text rendering options. + /// The text to draw. + /// Optional brush used to fill glyphs. + /// Optional pen used to outline glyphs. + public void DrawText( + RichTextOptions textOptions, + ReadOnlySpan text, + Brush? brush, + Pen? pen); + + /// + /// Draws layered glyph geometry using a monochrome projection. + /// + /// + /// For painted glyph layers, the implementation uses a coverage/compactness heuristic + /// to keep one dominant background-like layer as outline-only to preserve interior definition. + /// All non-painted layers are filled. + /// + /// Brush used to fill glyph layers. + /// Pen used to outline dominant painted layers. + /// Layered glyph geometry to draw. + public void DrawGlyphs( + Brush brush, + Pen pen, + IReadOnlyList glyphs); + + /// + /// Measures the logical advance of the text in pixel units. + /// + /// The text shaping and layout options. + /// The text to measure. + /// The logical advance rectangle of the text if it was to be rendered. + /// + /// This measurement reflects line-box height and horizontal or vertical text advance from the layout model. + /// It does not guarantee that all rendered glyph pixels fit within the returned rectangle. + /// Use for glyph ink bounds or + /// for the union of logical advance and rendered bounds. + /// + public RectangleF MeasureTextAdvance(RichTextOptions textOptions, ReadOnlySpan text); + + /// + /// Measures the rendered glyph bounds of the text in pixel units. + /// + /// The text shaping and layout options. + /// The text to measure. + /// The rendered glyph bounds of the text if it was to be rendered. + /// + /// This measures the tight ink bounds enclosing all rendered glyphs. The returned rectangle + /// may be smaller or larger than the logical advance and may have a non-zero origin. + /// Use for the logical layout box or + /// for the union of both. + /// + public RectangleF MeasureTextBounds(RichTextOptions textOptions, ReadOnlySpan text); + + /// + /// Measures the full renderable bounds of the text in pixel units. + /// + /// The text shaping and layout options. + /// The text to measure. + /// + /// The union of the logical advance rectangle and the rendered glyph bounds if the text was to be rendered. + /// + /// + /// The returned rectangle is in absolute coordinates and is large enough to contain both the logical advance + /// rectangle and the rendered glyph bounds. + /// Use this method when both typographic advance and rendered glyph overshoot must fit within the same rectangle. + /// + public RectangleF MeasureTextRenderableBounds(RichTextOptions textOptions, ReadOnlySpan text); + + /// + /// Measures the normalized rendered size of the text in pixel units. + /// + /// The text shaping and layout options. + /// The text to measure. + /// The rendered size of the text with the origin normalized to (0, 0). + /// + /// This is equivalent to measuring the rendered bounds and returning only the width and height. + /// Use when the returned X and Y offset are also required. + /// + public RectangleF MeasureTextSize(RichTextOptions textOptions, ReadOnlySpan text); + + /// + /// Measures the logical advance of each laid-out character entry in pixel units. + /// + /// The text shaping and layout options. + /// The text to measure. + /// The list of per-entry logical advances of the text if it was to be rendered. + /// Whether any of the entries had non-empty advances. + /// + /// Each entry reflects the typographic advance width and height for one character. + /// Use for per-character ink bounds or + /// for the union of both. + /// + public bool TryMeasureCharacterAdvances(RichTextOptions textOptions, ReadOnlySpan text, out ReadOnlySpan advances); + + /// + /// Measures the rendered glyph bounds of each laid-out character entry in pixel units. + /// + /// The text shaping and layout options. + /// The text to measure. + /// The list of per-entry rendered glyph bounds of the text if it was to be rendered. + /// Whether any of the entries had non-empty bounds. + /// + /// Each entry reflects the tight ink bounds of one rendered glyph. + /// Use for per-character logical advances or + /// for the union of both. + /// + public bool TryMeasureCharacterBounds(RichTextOptions textOptions, ReadOnlySpan text, out ReadOnlySpan bounds); + + /// + /// Measures the full renderable bounds of each laid-out character entry in pixel units. + /// + /// The text shaping and layout options. + /// The text to measure. + /// The list of per-entry renderable bounds of the text if it was to be rendered. + /// Whether any of the entries had non-empty bounds. + /// + /// Each returned rectangle is in absolute coordinates and is large enough to contain both the logical advance + /// rectangle and the rendered glyph bounds for the corresponding laid-out entry. + /// Use this when both typographic advance and rendered glyph overshoot must fit within the same rectangle. + /// + public bool TryMeasureCharacterRenderableBounds(RichTextOptions textOptions, ReadOnlySpan text, out ReadOnlySpan bounds); + + /// + /// Measures the normalized rendered size of each laid-out character entry in pixel units. + /// + /// The text shaping and layout options. + /// The text to measure. + /// The list of per-entry rendered sizes with the origin normalized to (0, 0). + /// Whether any of the entries had non-empty dimensions. + /// + /// This is equivalent to measuring per-character bounds and returning only the width and height. + /// Use when the returned X and Y offset are also required. + /// + public bool TryMeasureCharacterSizes(RichTextOptions textOptions, ReadOnlySpan text, out ReadOnlySpan sizes); + + /// + /// Gets the number of laid-out lines contained within the text. + /// + /// The text shaping and layout options. + /// The text to measure. + /// The laid-out line count. + public int CountTextLines(RichTextOptions textOptions, ReadOnlySpan text); + + /// + /// Gets per-line layout metrics for the supplied text. + /// + /// The text shaping and layout options. + /// The text to measure. + /// + /// An array of in pixel units, one entry per laid-out line. + /// + /// + /// + /// The returned and are expressed + /// in the primary flow direction for the active layout mode. + /// + /// + /// , , and + /// are line-box positions relative to the current line origin and are suitable for drawing guide lines. + /// + /// + /// Horizontal layouts: Start = X position, Extent = width. + /// Vertical layouts: Start = Y position, Extent = height. + /// + /// + public LineMetrics[] GetTextLineMetrics(RichTextOptions textOptions, ReadOnlySpan text); + + /// + /// Draws an image source region into a destination rectangle. + /// + /// The source image. + /// The source rectangle within . + /// The destination rectangle in local canvas coordinates. + /// + /// Optional resampler used when scaling or transforming the image. Defaults to . + /// + public void DrawImage( + Image image, + Rectangle sourceRect, + RectangleF destinationRect, + IResampler? sampler = null); + + /// + /// Flushes queued drawing commands to the target in submission order. + /// + public void Flush(); +} diff --git a/src/ImageSharp.Drawing/Processing/ImageBrush.cs b/src/ImageSharp.Drawing/Processing/ImageBrush.cs index 5a8062e30..e0a154a50 100644 --- a/src/ImageSharp.Drawing/Processing/ImageBrush.cs +++ b/src/ImageSharp.Drawing/Processing/ImageBrush.cs @@ -11,21 +11,6 @@ namespace SixLabors.ImageSharp.Drawing.Processing; /// public class ImageBrush : Brush { - /// - /// The image to paint. - /// - private readonly Image image; - - /// - /// The region of the source image we will be using to paint. - /// - private readonly RectangleF region; - - /// - /// The offet to apply to the source image while applying the imagebrush - /// - private readonly Point offset; - /// /// Initializes a new instance of the class. /// @@ -73,39 +58,54 @@ public ImageBrush(Image image, RectangleF region) /// public ImageBrush(Image image, RectangleF region, Point offset) { - this.image = image; - this.region = RectangleF.Intersect(image.Bounds, region); - this.offset = offset; + this.SourceImage = image; + this.SourceRegion = RectangleF.Intersect(image.Bounds, region); + this.Offset = offset; } + /// + /// Gets the source image used by this brush. + /// + public Image SourceImage { get; } + + /// + /// Gets the source region within the image. + /// + public RectangleF SourceRegion { get; } + + /// + /// Gets the offset applied to the brush origin. + /// + public Point Offset { get; } + /// public override bool Equals(Brush? other) { if (other is ImageBrush ib) { - return ib.image == this.image && ib.region == this.region; + return ib.SourceImage == this.SourceImage && ib.SourceRegion == this.SourceRegion; } return false; } /// - public override int GetHashCode() => HashCode.Combine(this.image, this.region); + public override int GetHashCode() => HashCode.Combine(this.SourceImage, this.SourceRegion); /// public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) { - if (this.image is Image specificImage) + if (this.SourceImage is Image specificImage) { - return new ImageBrushApplicator(configuration, options, source, specificImage, region, this.region, this.offset, false); + return new ImageBrushApplicator(configuration, options, targetRegion, specificImage, region, this.SourceRegion, this.Offset, false); } - specificImage = this.image.CloneAs(); - return new ImageBrushApplicator(configuration, options, source, specificImage, region, this.region, this.offset, true); + specificImage = this.SourceImage.CloneAs(); + return new ImageBrushApplicator(configuration, options, targetRegion, specificImage, region, this.SourceRegion, this.Offset, true); } /// @@ -140,7 +140,7 @@ private class ImageBrushApplicator : BrushApplicator /// /// The configuration instance to use when performing operations. /// The graphics options. - /// The target image. + /// The destination pixel region. /// The image. /// The region of the target image we will be drawing to. /// The region of the source image we will be using to source pixels to draw from. @@ -149,13 +149,13 @@ private class ImageBrushApplicator : BrushApplicator public ImageBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame target, + Buffer2DRegion destinationRegion, Image image, RectangleF targetRegion, RectangleF sourceRegion, Point offset, bool shouldDisposeImage) - : base(configuration, options, target) + : base(configuration, options, destinationRegion) { this.sourceImage = image; this.sourceFrame = image.Frames.RootFrame; @@ -221,7 +221,9 @@ public override void Apply(Span scanline, int x, int y) overlaySpan[i] = sourceRow[sourceX]; } - Span destinationRow = this.Target.PixelBuffer.DangerousGetRowSpan(y).Slice(x, scanline.Length); + int localY = y - this.TargetRegion.Rectangle.Y; + int localX = x - this.TargetRegion.Rectangle.X; + Span destinationRow = this.TargetRegion.DangerousGetRowSpan(localY).Slice(localX, scanline.Length); this.Blender.Blend( this.Configuration, destinationRow, diff --git a/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs index 22692dc0d..6452ec915 100644 --- a/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Drawing.Processing; /// @@ -9,10 +12,6 @@ namespace SixLabors.ImageSharp.Drawing.Processing; /// public sealed class LinearGradientBrush : GradientBrush { - private readonly PointF startPoint; - private readonly PointF endPoint; - private readonly PointF? rotationPoint; - /// /// Initializes a new instance of the class using /// a start and end point. @@ -28,9 +27,8 @@ public LinearGradientBrush( params ColorStop[] colorStops) : base(repetitionMode, colorStops) { - this.startPoint = p0; - this.endPoint = p1; - this.rotationPoint = null; + this.StartPoint = p0; + this.EndPoint = p1; } /// @@ -52,20 +50,37 @@ public LinearGradientBrush( params ColorStop[] colorStops) : base(repetitionMode, colorStops) { - this.startPoint = p0; - this.endPoint = p1; - this.rotationPoint = rotationPoint; + ResolveAxis(p0, p1, rotationPoint, out PointF start, out PointF end); + this.StartPoint = start; + this.EndPoint = end; } + /// + /// Gets the start point of the gradient axis. + /// + public PointF StartPoint { get; } + + /// + /// Gets the end point of the gradient axis. + /// + public PointF EndPoint { get; } + + /// + public override Brush Transform(Matrix4x4 matrix) + => new LinearGradientBrush( + PointF.Transform(this.StartPoint, matrix), + PointF.Transform(this.EndPoint, matrix), + this.RepetitionMode, + this.ColorStopsArray); + /// public override bool Equals(Brush? other) { if (other is LinearGradientBrush brush) { return base.Equals(other) - && this.startPoint.Equals(brush.startPoint) - && this.endPoint.Equals(brush.endPoint) - && Nullable.Equals(this.rotationPoint, brush.rotationPoint); + && this.StartPoint.Equals(brush.StartPoint) + && this.EndPoint.Equals(brush.EndPoint); } return false; @@ -73,22 +88,61 @@ public override bool Equals(Brush? other) /// public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), this.startPoint, this.endPoint, this.rotationPoint); + => HashCode.Combine(base.GetHashCode(), this.StartPoint, this.EndPoint); + + /// + /// Resolves a three-point gradient axis into a two-point axis by projecting + /// the gradient vector (p0→p1) onto the perpendicular of the rotation vector (p0→rotationPoint). + /// This follows the COLRv1 font specification for rotated linear gradients. + /// + /// The gradient start point. + /// The gradient vector endpoint. + /// The rotation reference point. + /// The resolved start point of the gradient axis. + /// The resolved end point of the gradient axis. + private static void ResolveAxis(PointF p0, PointF p1, PointF rotationPoint, out PointF start, out PointF end) + { + // Gradient vector from p0 to p1. + float vx = p1.X - p0.X; + float vy = p1.Y - p0.Y; + + // Rotation vector from p0 to rotation point. + float rx = rotationPoint.X - p0.X; + float ry = rotationPoint.Y - p0.Y; + + // Perpendicular to the rotation vector. + float nx = ry; + float ny = -rx; + + float ndotn = (nx * nx) + (ny * ny); + if (ndotn == 0f) + { + // Degenerate: p0 == rotationPoint, fall back to original axis. + start = p0; + end = p1; + } + else + { + // Project the gradient vector onto the perpendicular direction. + float vdotn = (vx * nx) + (vy * ny); + float scale = vdotn / ndotn; + start = p0; + end = new PointF(p0.X + (scale * nx), p0.Y + (scale * ny)); + } + } /// public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) => new LinearGradientBrushApplicator( configuration, options, - source, - this.startPoint, - this.endPoint, - this.rotationPoint, - this.ColorStops, + targetRegion, + this, + this.ColorStopsArray, this.RepetitionMode); /// @@ -99,129 +153,46 @@ private sealed class LinearGradientBrushApplicator : GradientBrushApplic where TPixel : unmanaged, IPixel { private readonly PointF start; - private readonly PointF end; private readonly float alongX; private readonly float alongY; - private readonly float acrossX; - private readonly float acrossY; private readonly float alongsSquared; - private readonly float length; /// /// Initializes a new instance of the class. /// /// The ImageSharp configuration. /// The graphics options. - /// The target image frame. - /// The gradient start point. - /// The gradient end point. - /// The optional rotation point. + /// The destination pixel region. + /// The linear gradient brush. /// The gradient color stops. /// Defines how the gradient repeats. public LinearGradientBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, - PointF p0, - PointF p1, - PointF? p2, + Buffer2DRegion targetRegion, + LinearGradientBrush brush, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) - : base(configuration, options, source, colorStops, repetitionMode) + : base(configuration, options, targetRegion, colorStops, repetitionMode) { - // Determine whether this is a simple linear gradient (2 points) - // or a rotated one (3 points). - if (p2 is null) - { - // Classic SVG-style gradient from start -> end. - this.start = p0; - this.end = p1; - } - else - { - // Compute the rotated gradient axis per COLRv1 rules. - // p0 = start, p1 = gradient vector, p2 = rotation reference. - float vx = p1.X - p0.X; - float vy = p1.Y - p0.Y; - float rx = p2.Value.X - p0.X; - float ry = p2.Value.Y - p0.Y; - - // n = perpendicular to rotation vector - float nx = ry; - float ny = -rx; - - // Avoid divide-by-zero if p0 == p2. - float ndotn = (nx * nx) + (ny * ny); - if (ndotn == 0f) - { - this.start = p0; - this.end = p1; - } - else - { - // Project p1 - p0 onto perpendicular direction. - float vdotn = (vx * nx) + (vy * ny); - float scale = vdotn / ndotn; - - // The derived endpoint after rotation. - this.start = p0; - this.end = new PointF(p0.X + (scale * nx), p0.Y + (scale * ny)); - } - } - - // Calculate axis vectors. - this.alongX = this.end.X - this.start.X; - this.alongY = this.end.Y - this.start.Y; + this.start = brush.StartPoint; - // Perpendicular axis vector. - this.acrossX = this.alongY; - this.acrossY = -this.alongX; - - // Precompute squared length and actual length for later use. + this.alongX = brush.EndPoint.X - this.start.X; + this.alongY = brush.EndPoint.Y - this.start.Y; this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY); - this.length = MathF.Sqrt(this.alongsSquared); } /// protected override float PositionOnGradient(float x, float y) { - // Degenerate case: gradient length == 0, use final stop color. if (this.alongsSquared == 0f) { return 1f; } - // Fast path for horizontal gradients. - if (this.acrossX == 0f) - { - float denom = this.end.X - this.start.X; - return denom != 0f ? (x - this.start.X) / denom : 1f; - } - - // Fast path for vertical gradients. - if (this.acrossY == 0f) - { - float denom = this.end.Y - this.start.Y; - return denom != 0f ? (y - this.start.Y) / denom : 1f; - } - - // General case: project sample point onto the gradient axis. float deltaX = x - this.start.X; float deltaY = y - this.start.Y; - - // Compute perpendicular projection scalar. - float k = ((this.alongY * deltaX) - (this.alongX * deltaY)) / this.alongsSquared; - - // Determine projected point on the gradient line. - float projX = x - (k * this.alongY); - float projY = y + (k * this.alongX); - - // Compute distance from gradient start to projected point. - float dx = projX - this.start.X; - float dy = projY - this.start.Y; - - // Normalize to [0,1] range along the gradient length. - return this.length > 0f ? MathF.Sqrt((dx * dx) + (dy * dy)) / this.length : 1f; + return ((deltaX * this.alongX) + (deltaY * this.alongY)) / this.alongsSquared; } } } diff --git a/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs b/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs index ef3154273..745c7efe4 100644 --- a/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs @@ -2,7 +2,8 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using SixLabors.ImageSharp.Drawing.Utilities; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Drawing.Processing; @@ -88,12 +89,12 @@ public override int GetHashCode() public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) => new PathGradientBrushApplicator( configuration, options, - source, + targetRegion, this.edges, this.centerColor, this.hasSpecialCenterColor); @@ -156,7 +157,7 @@ public bool Intersect( Vector2 start, Vector2 end, ref Vector2 ip) => - Utilities.Intersect.LineSegmentToLineSegmentIgnoreCollinear(start, end, this.Start, this.End, ref ip); + PolygonUtilities.LineSegmentToLineSegmentIgnoreCollinear(start, end, this.Start, this.End, ref ip); public Vector4 ColorAt(float distance) { @@ -210,18 +211,18 @@ private sealed class PathGradientBrushApplicator : BrushApplicator /// The configuration instance to use when performing operations. /// The graphics options. - /// The source image. + /// The destination pixel region. /// Edges of the polygon. /// Color at the center of the gradient area to which the other colors converge. /// Whether the center color is different from a smooth gradient between the edges. public PathGradientBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, IList edges, Color centerColor, bool hasSpecialCenterColor) - : base(configuration, options, source) + : base(configuration, options, targetRegion) { this.edges = edges; Vector2[] points = [.. edges.Select(s => s.Start)]; @@ -232,14 +233,15 @@ public PathGradientBrushApplicator( this.centerPixel = centerColor.ToPixel(); this.maxDistance = points.Select(p => p - this.center).Max(d => d.Length()); this.transparentPixel = Color.Transparent.ToPixel(); - this.blenderBuffers = new ThreadLocalBlenderBuffers(configuration.MemoryAllocator, source.Width); + this.blenderBuffers = new ThreadLocalBlenderBuffers(configuration.MemoryAllocator, targetRegion.Width); } internal TPixel this[int x, int y] { get { - Vector2 point = new(x, y); + // Match other gradient brushes by evaluating at pixel centers. + Vector2 point = new(x + 0.5F, y + 0.5F); if (point == this.center) { @@ -313,7 +315,9 @@ public override void Apply(Span scanline, int x, int y) } } - Span destinationRow = this.Target.PixelBuffer.DangerousGetRowSpan(y).Slice(x, scanline.Length); + int localY = y - this.TargetRegion.Rectangle.Y; + int localX = x - this.TargetRegion.Rectangle.X; + Span destinationRow = this.TargetRegion.DangerousGetRowSpan(localY).Slice(localX, scanline.Length); this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlays, amounts); } @@ -342,7 +346,7 @@ protected override void Dispose(bool disposing) Vector2 ip = default; Vector2 closestIntersection = default; Edge? closestEdge = null; - const float minDistance = float.MaxValue; + float minDistance = float.MaxValue; foreach (Edge edge in this.edges) { if (!edge.Intersect(start, end, ref ip)) @@ -350,9 +354,10 @@ protected override void Dispose(bool disposing) continue; } - float d = Vector2.DistanceSquared(start, end); + float d = Vector2.DistanceSquared(start, ip); if (d < minDistance) { + minDistance = d; closestEdge = edge; closestIntersection = ip; } diff --git a/src/ImageSharp.Drawing/Processing/PatternBrush.cs b/src/ImageSharp.Drawing/Processing/PatternBrush.cs index 92bf3db83..3c3372bbe 100644 --- a/src/ImageSharp.Drawing/Processing/PatternBrush.cs +++ b/src/ImageSharp.Drawing/Processing/PatternBrush.cs @@ -2,7 +2,8 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using SixLabors.ImageSharp.Drawing.Utilities; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Drawing.Processing; @@ -83,6 +84,11 @@ internal PatternBrush(PatternBrush brush) this.patternVector = brush.patternVector; } + /// + /// Gets the pattern color matrix. + /// + public DenseMatrix Pattern => this.pattern; + /// public override bool Equals(Brush? other) { @@ -103,12 +109,12 @@ public override int GetHashCode() public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) => new PatternBrushApplicator( configuration, options, - source, + targetRegion, this.pattern.ToPixelMatrix()); /// @@ -127,17 +133,17 @@ private sealed class PatternBrushApplicator : BrushApplicator /// /// The configuration instance to use when performing operations. /// The graphics options. - /// The source image. + /// The destination pixel region. /// The pattern. public PatternBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, in DenseMatrix pattern) - : base(configuration, options, source) + : base(configuration, options, targetRegion) { this.pattern = pattern; - this.blenderBuffers = new ThreadLocalBlenderBuffers(configuration.MemoryAllocator, source.Width); + this.blenderBuffers = new ThreadLocalBlenderBuffers(configuration.MemoryAllocator, targetRegion.Width); } internal TPixel this[int x, int y] @@ -161,13 +167,15 @@ public override void Apply(Span scanline, int x, int y) for (int i = 0; i < scanline.Length; i++) { - amounts[i] = NumericUtilities.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F); + amounts[i] = Math.Clamp(scanline[i] * this.Options.BlendPercentage, 0, 1F); int patternX = (x + i) % this.pattern.Columns; overlays[i] = this.pattern[patternY, patternX]; } - Span destinationRow = this.Target.PixelBuffer.DangerousGetRowSpan(y).Slice(x, scanline.Length); + int localY = y - this.TargetRegion.Rectangle.Y; + int localX = x - this.TargetRegion.Rectangle.X; + Span destinationRow = this.TargetRegion.DangerousGetRowSpan(localY).Slice(localX, scanline.Length); this.Blender.Blend( this.Configuration, destinationRow, diff --git a/src/ImageSharp.Drawing/Processing/PatternPen.cs b/src/ImageSharp.Drawing/Processing/PatternPen.cs index f6da8ee04..75b7e32bb 100644 --- a/src/ImageSharp.Drawing/Processing/PatternPen.cs +++ b/src/ImageSharp.Drawing/Processing/PatternPen.cs @@ -75,5 +75,5 @@ public override bool Equals(Pen? other) /// public override IPath GeneratePath(IPath path, float strokeWidth) - => path.GenerateOutline(strokeWidth, this.StrokePattern, this.StrokeOptions); + => path.GenerateOutline(strokeWidth, this.StrokePattern.Span, this.StrokeOptions); } diff --git a/src/ImageSharp.Drawing/Processing/Pen.cs b/src/ImageSharp.Drawing/Processing/Pen.cs index e3fbd3094..0ad0b0b39 100644 --- a/src/ImageSharp.Drawing/Processing/Pen.cs +++ b/src/ImageSharp.Drawing/Processing/Pen.cs @@ -80,7 +80,7 @@ protected Pen(PenOptions options) public float StrokeWidth { get; } /// - public ReadOnlySpan StrokePattern => this.pattern; + public ReadOnlyMemory StrokePattern => this.pattern; /// public StrokeOptions StrokeOptions { get; } @@ -107,7 +107,7 @@ public virtual bool Equals(Pen? other) && this.StrokeWidth == other.StrokeWidth && this.StrokeFill.Equals(other.StrokeFill) && this.StrokeOptions.Equals(other.StrokeOptions) - && this.StrokePattern.SequenceEqual(other.StrokePattern); + && this.StrokePattern.Span.SequenceEqual(other.StrokePattern.Span); /// public override bool Equals(object? obj) => this.Equals(obj as Pen); diff --git a/src/ImageSharp.Drawing/Processing/ProcessWithCanvasExtensions.cs b/src/ImageSharp.Drawing/Processing/ProcessWithCanvasExtensions.cs new file mode 100644 index 000000000..c27880c9e --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/ProcessWithCanvasExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Processing; + +/// +/// Represents a drawing callback executed against a . +/// +/// The drawing canvas for the current frame. +public delegate void CanvasAction(IDrawingCanvas canvas); + +/// +/// Adds extensions that execute drawing callbacks against all frames through . +/// +public static class ProcessWithCanvasExtensions +{ + /// + /// Executes for each image frame using drawing options from the current context. + /// + /// The source image processing context. + /// The drawing callback to execute for each frame. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessWithCanvas( + this IImageProcessingContext source, + CanvasAction action) + => source.ProcessWithCanvas(source.GetDrawingOptions(), action); + + /// + /// Executes for each image frame using the supplied drawing options. + /// + /// The source image processing context. + /// The drawing options. + /// The drawing callback to execute for each frame. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessWithCanvas( + this IImageProcessingContext source, + DrawingOptions options, + CanvasAction action) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(action, nameof(action)); + + return source.ApplyProcessor(new ProcessWithCanvasProcessor(options, action)); + } +} diff --git a/src/ImageSharp.Drawing/Processing/ProcessWithCanvasProcessor.cs b/src/ImageSharp.Drawing/Processing/ProcessWithCanvasProcessor.cs new file mode 100644 index 000000000..25b105f6c --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/ProcessWithCanvasProcessor.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing.Processors; + +namespace SixLabors.ImageSharp.Drawing.Processing; + +/// +/// Defines a processor that executes a canvas callback for each image frame. +/// +public sealed class ProcessWithCanvasProcessor : IImageProcessor +{ + /// + /// Initializes a new instance of the class. + /// + /// The drawing options. + /// The per-frame canvas callback. + public ProcessWithCanvasProcessor(DrawingOptions options, CanvasAction action) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(action, nameof(action)); + + this.Options = options; + this.Action = action; + } + + /// + /// Gets the drawing options. + /// + public DrawingOptions Options { get; } + + internal CanvasAction Action { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor( + Configuration configuration, + Image source, + Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new ProcessWithCanvasProcessor(configuration, this, source, sourceRectangle); +} diff --git a/src/ImageSharp.Drawing/Processing/ProcessWithCanvasProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/ProcessWithCanvasProcessor{TPixel}.cs new file mode 100644 index 000000000..130f8796b --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/ProcessWithCanvasProcessor{TPixel}.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing.Processors; + +namespace SixLabors.ImageSharp.Drawing.Processing; + +/// +/// Executes a per-frame canvas callback for a specific pixel type. +/// +/// The pixel format. +internal sealed class ProcessWithCanvasProcessor : ImageProcessor + where TPixel : unmanaged, IPixel +{ + private readonly ProcessWithCanvasProcessor definition; + private readonly CanvasAction action; + + /// + /// Initializes a new instance of the class. + /// + /// The processing configuration. + /// The processor definition. + /// The source image. + /// The source bounds. + public ProcessWithCanvasProcessor( + Configuration configuration, + ProcessWithCanvasProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.definition = definition; + this.action = definition.Action; + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + using DrawingCanvas canvas = DrawingCanvas.FromFrame(source, this.definition.Options); + this.action(canvas); + } +} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/ClipPathProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/ClipPathProcessor.cs deleted file mode 100644 index a6a9bb475..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/ClipPathProcessor.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -/// -/// Allows the recursive application of processing operations against an image within a given region. -/// -public class ClipPathProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The drawing options. - /// The defining the region to operate within. - /// The operation to perform on the source. - public ClipPathProcessor(DrawingOptions options, IPath path, Action operation) - { - this.Options = options; - this.Region = path; - this.Operation = operation; - } - - /// - /// Gets the drawing options. - /// - public DrawingOptions Options { get; } - - /// - /// Gets the defining the region to operate within. - /// - public IPath Region { get; } - - /// - /// Gets the operation to perform on the source. - /// - public Action Operation { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor( - Configuration configuration, - Image source, - Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new ClipPathProcessor(this, source, configuration, sourceRectangle); -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/ClipPathProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/ClipPathProcessor{TPixel}.cs deleted file mode 100644 index 5e4089c68..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/ClipPathProcessor{TPixel}.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -/// -/// Applies a processing operation to a clipped path region by constraining the operation's input domain -/// to the bounds of the path, then using the processed result as an image brush to fill the path. -/// -/// The type of pixel. -internal class ClipPathProcessor : IImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly ClipPathProcessor definition; - private readonly Image source; - private readonly Configuration configuration; - private readonly Rectangle sourceRectangle; - - public ClipPathProcessor(ClipPathProcessor definition, Image source, Configuration configuration, Rectangle sourceRectangle) - { - this.definition = definition; - this.source = source; - this.configuration = configuration; - this.sourceRectangle = sourceRectangle; - } - - public void Dispose() - { - } - - public void Execute() - { - // Bounds in drawing are floating point. We must conservatively cover the entire shape bounds. - RectangleF boundsF = this.definition.Region.Bounds; - - int left = (int)MathF.Floor(boundsF.Left); - int top = (int)MathF.Floor(boundsF.Top); - int right = (int)MathF.Ceiling(boundsF.Right); - int bottom = (int)MathF.Ceiling(boundsF.Bottom); - - Rectangle crop = Rectangle.FromLTRB(left, top, right, bottom); - - // Constrain the operation to the intersection of the requested bounds and source region. - Rectangle clipped = Rectangle.Intersect(this.sourceRectangle, crop); - - if (clipped.Width <= 0 || clipped.Height <= 0) - { - return; - } - - Action operation = this.definition.Operation; - - // Run the operation on the clipped context so only pixels inside the clip are affected, - // matching the expected semantics of clipping in other graphics APIs. - using Image clone = this.source.Clone(ctx => operation(ctx.Crop(clipped))); - - // Use the clone as a brush source so only the clipped result contributes to the fill, - // keeping the effect confined to the clipped region. - Point brushOffset = new( - clipped.X - (int)MathF.Floor(boundsF.Left), - clipped.Y - (int)MathF.Floor(boundsF.Top)); - - ImageBrush brush = new(clone, clone.Bounds, brushOffset); - - // Fill the shape using the image brush. - FillPathProcessor processor = new(this.definition.Options, brush, this.definition.Region); - using IImageProcessor pixelProcessor = processor.CreatePixelSpecificProcessor(this.configuration, this.source, this.sourceRectangle); - pixelProcessor.Execute(); - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawPathProcessor.cs deleted file mode 100644 index 5b3a5cc88..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawPathProcessor.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -/// -/// Defines a processor to fill pixels withing a given -/// with the given and blending defined by the given . -/// -public class DrawPathProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The graphics options. - /// The details how to outline the region of interest. - /// The path to be filled. - public DrawPathProcessor(DrawingOptions options, Pen pen, IPath path) - { - this.Path = path; - this.Pen = pen; - this.Options = options; - } - - /// - /// Gets the used for filling the destination image. - /// - public Pen Pen { get; } - - /// - /// Gets the path that this processor applies to. - /// - public IPath Path { get; } - - /// - /// Gets the defining how to blend the brush pixels over the image pixels. - /// - public DrawingOptions Options { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - { - // Offset drawlines to align drawing outlines to pixel centers. - // The global transform is applied in the FillPathProcessor. - IPath outline = this.Pen.GeneratePath(this.Path.Transform(Matrix3x2.CreateTranslation(0.5F, 0.5F))); - - DrawingOptions effectiveOptions = this.Options; - - // Non-normalized stroked output can contain overlaps/self-intersections. - // Rasterizing these contours with non-zero winding matches the intended stroke semantics. - if (!this.Pen.StrokeOptions.NormalizeOutput && - this.Options.ShapeOptions.IntersectionRule != IntersectionRule.NonZero) - { - ShapeOptions shapeOptions = this.Options.ShapeOptions.DeepClone(); - shapeOptions.IntersectionRule = IntersectionRule.NonZero; - - effectiveOptions = new DrawingOptions(this.Options.GraphicsOptions, shapeOptions, this.Options.Transform); - } - - return new FillPathProcessor(effectiveOptions, this.Pen.StrokeFill, outline) - .CreatePixelSpecificProcessor(configuration, source, sourceRectangle); - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillPathProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillPathProcessor.cs deleted file mode 100644 index 5dfadb974..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillPathProcessor.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -/// -/// Defines a processor to fill pixels withing a given -/// with the given and blending defined by the given . -/// -public class FillPathProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The graphics options. - /// The details how to fill the region of interest. - /// The logic path to be filled. - public FillPathProcessor(DrawingOptions options, Brush brush, IPath path) - { - this.Region = path; - this.Brush = brush; - this.Options = options; - } - - /// - /// Gets the used for filling the destination image. - /// - public Brush Brush { get; } - - /// - /// Gets the logic path that this processor applies to. - /// - public IPath Region { get; } - - /// - /// Gets the defining how to blend the brush pixels over the image pixels. - /// - public DrawingOptions Options { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - { - IPath shape = this.Region.Transform(this.Options.Transform); - - if (shape is RectangularPolygon rectPoly) - { - RectangleF rectF = new(rectPoly.Location, rectPoly.Size); - Rectangle rect = (Rectangle)rectF; - if (!this.Options.GraphicsOptions.Antialias || rectF == rect) - { - // Cast as in and back are the same or we are using anti-aliasing - return new FillProcessor(this.Options, this.Brush) - .CreatePixelSpecificProcessor(configuration, source, rect); - } - } - - // Clone the definition so we can pass the transformed path. - FillPathProcessor definition = new(this.Options, this.Brush, shape); - return new FillPathProcessor(configuration, definition, source, sourceRectangle); - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillPathProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillPathProcessor{TPixel}.cs deleted file mode 100644 index 0788cb6fc..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillPathProcessor{TPixel}.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing.Backends; -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -/// -/// Uses a brush and a shape to fill the shape with contents of the brush. -/// -/// The type of the color. -/// -internal class FillPathProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly FillPathProcessor definition; - private readonly IPath path; - private readonly Rectangle bounds; - - /// - /// Initializes a new instance of the class. - /// - /// The processing configuration. - /// The processor definition. - /// The source image. - /// The source bounds. - public FillPathProcessor( - Configuration configuration, - FillPathProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - IPath path = definition.Region; - int left = (int)MathF.Floor(path.Bounds.Left); - int top = (int)MathF.Floor(path.Bounds.Top); - int right = (int)MathF.Ceiling(path.Bounds.Right); - int bottom = (int)MathF.Ceiling(path.Bounds.Bottom); - - this.bounds = Rectangle.FromLTRB(left, top, right, bottom); - this.path = path.AsClosedPath(); - this.definition = definition; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - Configuration configuration = this.Configuration; - ShapeOptions shapeOptions = this.definition.Options.ShapeOptions; - GraphicsOptions graphicsOptions = this.definition.Options.GraphicsOptions; - Brush brush = this.definition.Brush; - - // Align start/end positions. - Rectangle interest = Rectangle.Intersect(this.bounds, source.Bounds); - if (interest.Equals(Rectangle.Empty)) - { - return; // No effect inside image; - } - - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - IDrawingBackend drawingBackend = configuration.GetDrawingBackend(); - RasterizationMode rasterizationMode = graphicsOptions.Antialias ? RasterizationMode.Antialiased : RasterizationMode.Aliased; - RasterizerOptions rasterizerOptions = new( - interest, - shapeOptions.IntersectionRule, - rasterizationMode, - RasterizerSamplingOrigin.PixelBoundary); - - // The backend owns rasterization/compositing details. Processors only submit - // operation-level data (path, brush, options, bounds). - drawingBackend.FillPath( - configuration, - source, - this.path, - brush, - graphicsOptions, - rasterizerOptions, - this.bounds, - allocator); - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs deleted file mode 100644 index cec760e71..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -/// -/// Defines a processor to fill an with the given -/// using blending defined by the given . -/// -public class FillProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The defining how to blend the brush pixels over the image pixels. - /// The brush to use for filling. - public FillProcessor(DrawingOptions options, Brush brush) - { - this.Brush = brush; - this.Options = options; - } - - /// - /// Gets the used for filling the destination image. - /// - public Brush Brush { get; } - - /// - /// Gets the defining how to blend the brush pixels over the image pixels. - /// - public DrawingOptions Options { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new FillProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs deleted file mode 100644 index 9e1e3c86f..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; - -/// -/// Using the brush as a source of pixels colors blends the brush color with source. -/// -/// The pixel format. -internal class FillProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private readonly FillProcessor definition; - - public FillProcessor(Configuration configuration, FillProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds); - if (interest.Width == 0 || interest.Height == 0) - { - return; - } - - Configuration configuration = this.Configuration; - Brush brush = this.definition.Brush; - GraphicsOptions options = this.definition.Options.GraphicsOptions; - - // If there's no reason for blending, then avoid it. - if (this.IsSolidBrushWithoutBlending(out SolidBrush? solidBrush)) - { - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration) - .MultiplyMinimumPixelsPerTask(4); - - TPixel colorPixel = solidBrush.Color.ToPixel(); - - FillProcessor.SolidBrushRowIntervalOperation solidOperation = new(interest, source, colorPixel); - ParallelRowIterator.IterateRowIntervals( - interest, - parallelSettings, - in solidOperation); - - return; - } - - using IMemoryOwner amount = configuration.MemoryAllocator.Allocate(interest.Width); - using BrushApplicator applicator = brush.CreateApplicator( - configuration, - options, - source, - this.SourceRectangle); - - amount.Memory.Span.Fill(1F); - - FillProcessor.RowIntervalOperation operation = new(interest, applicator, amount.Memory); - ParallelRowIterator.IterateRowIntervals( - configuration, - interest, - in operation); - } - - private bool IsSolidBrushWithoutBlending([NotNullWhen(true)] out SolidBrush? solidBrush) - { - solidBrush = this.definition.Brush as SolidBrush; - - if (solidBrush is null) - { - return false; - } - - return this.definition.Options.GraphicsOptions.IsOpaqueColorWithoutBlending(solidBrush.Color); - } - - private readonly struct SolidBrushRowIntervalOperation : IRowIntervalOperation - { - private readonly Rectangle bounds; - private readonly ImageFrame source; - private readonly TPixel color; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SolidBrushRowIntervalOperation(Rectangle bounds, ImageFrame source, TPixel color) - { - this.bounds = bounds; - this.source = source; - this.color = color; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(in RowInterval rows) - { - for (int y = rows.Min; y < rows.Max; y++) - { - this.source.PixelBuffer.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width).Fill(this.color); - } - } - } - - private readonly struct RowIntervalOperation : IRowIntervalOperation - { - private readonly Memory amount; - private readonly Rectangle bounds; - private readonly BrushApplicator applicator; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public RowIntervalOperation(Rectangle bounds, BrushApplicator applicator, Memory amount) - { - this.bounds = bounds; - this.applicator = applicator; - this.amount = amount; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(in RowInterval rows) - { - Span amountSpan = this.amount.Span; - int x = this.bounds.X; - for (int y = rows.Min; y < rows.Max; y++) - { - this.applicator.Apply(amountSpan, x, y); - } - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs deleted file mode 100644 index 0eba43ace..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Text; - -/// -/// Defines a processor to draw text on an . -/// -public class DrawTextProcessor : IImageProcessor -{ - /// - /// Initializes a new instance of the class. - /// - /// The drawing options. - /// The text rendering options. - /// The text we want to render - /// The brush to source pixel colors from. - /// The pen to outline text with. - public DrawTextProcessor(DrawingOptions drawingOptions, RichTextOptions textOptions, string text, Brush? brush, Pen? pen) - { - Guard.NotNull(text, nameof(text)); - if (brush is null && pen is null) - { - throw new ArgumentException($"Expected a {nameof(brush)} or {nameof(pen)}. Both were null"); - } - - this.DrawingOptions = drawingOptions; - this.TextOptions = textOptions; - this.Text = text; - this.Brush = brush; - this.Pen = pen; - } - - /// - /// Gets the brush used to fill the glyphs. - /// - public Brush? Brush { get; } - - /// - /// Gets the defining blending modes and shape drawing settings. - /// - public DrawingOptions DrawingOptions { get; } - - /// - /// Gets the defining text-specific drawing settings. - /// - public RichTextOptions TextOptions { get; } - - /// - /// Gets the text to draw. - /// - public string Text { get; } - - /// - /// Gets the pen used for outlining the text, if Null then we will not outline - /// - public Pen? Pen { get; } - - /// - /// Gets the location to draw the text at. - /// - public PointF Location { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new DrawTextProcessor(configuration, this, source, sourceRectangle); -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs deleted file mode 100644 index a8f60b240..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.Fonts.Rendering; -using SixLabors.ImageSharp.Drawing.Text; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Text; - -/// -/// Using the brush as a source of pixels colors blends the brush color with source. -/// -/// The pixel format. -internal class DrawTextProcessor : ImageProcessor - where TPixel : unmanaged, IPixel -{ - private RichTextGlyphRenderer? textRenderer; - private readonly DrawTextProcessor definition; - - public DrawTextProcessor(Configuration configuration, DrawTextProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; - - protected override void BeforeImageApply() - { - base.BeforeImageApply(); - - // Do everything at the image level as we are delegating - // the processing down to other processors - RichTextOptions textOptions = ConfigureOptions(this.definition.TextOptions); - - this.textRenderer = new RichTextGlyphRenderer( - textOptions, - this.definition.DrawingOptions, - this.Configuration.MemoryAllocator, - this.Configuration.GetDrawingBackend(), - this.definition.Pen, - this.definition.Brush); - - TextRenderer renderer = new(this.textRenderer); - renderer.RenderText(this.definition.Text, textOptions); - } - - protected override void AfterImageApply() - { - base.AfterImageApply(); - this.textRenderer?.Dispose(); - this.textRenderer = null; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - void Draw(IEnumerable operations) - { - foreach (DrawingOperation operation in operations) - { - GraphicsOptions graphicsOptions = - this.definition.DrawingOptions.GraphicsOptions.CloneOrReturnForRules( - operation.PixelAlphaCompositionMode, - operation.PixelColorBlendingMode); - - using BrushApplicator app = operation.Brush.CreateApplicator( - this.Configuration, - graphicsOptions, - source, - this.SourceRectangle); - - Buffer2D buffer = operation.Map; - int startY = operation.RenderLocation.Y; - int startX = operation.RenderLocation.X; - int offsetSpan = 0; - - if (startY + buffer.Height < 0) - { - continue; - } - - if (startX + buffer.Width < 0) - { - continue; - } - - if (startX < 0) - { - offsetSpan = -startX; - startX = 0; - } - - if (startX >= source.Width) - { - continue; - } - - int firstRow = 0; - if (startY < 0) - { - firstRow = -startY; - } - - int maxWidth = source.Width - startX; - int maxHeight = source.Height - startY; - int end = Math.Min(operation.Map.Height, maxHeight); - - for (int row = firstRow; row < end; row++) - { - int y = startY + row; - Span span = buffer.DangerousGetRowSpan(row).Slice(offsetSpan, Math.Min(buffer.Width - offsetSpan, maxWidth)); - app.Apply(span, startX, y); - } - } - } - - // Not null, initialized in earlier event. - if (this.textRenderer!.DrawingOperations.Count > 0) - { - Draw(this.textRenderer.DrawingOperations.OrderBy(x => x.RenderPass)); - } - } - - private static RichTextOptions ConfigureOptions(RichTextOptions options) - { - // When a path is specified we should explicitly follow that path - // and not adjust the origin. Any translation should be applied to the path. - if (options.Path is not null && options.Origin != Vector2.Zero) - { - return new RichTextOptions(options) - { - Origin = Vector2.Zero, - Path = options.Path.Translate(options.Origin) - }; - } - - return options; - } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawingOperation.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawingOperation.cs deleted file mode 100644 index 1914f4bb5..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawingOperation.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Text; - -internal struct DrawingOperation -{ - public Buffer2D Map { get; set; } - - public IPath Path { get; set; } - - public byte RenderPass { get; set; } - - public Point RenderLocation { get; set; } - - public Brush Brush { get; internal set; } - - public PixelAlphaCompositionMode PixelAlphaCompositionMode { get; set; } - - public PixelColorBlendingMode PixelColorBlendingMode { get; set; } -} diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.cs deleted file mode 100644 index 6436d67ef..000000000 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.cs +++ /dev/null @@ -1,704 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.Fonts; -using SixLabors.Fonts.Rendering; -using SixLabors.Fonts.Unicode; -using SixLabors.ImageSharp.Drawing.Processing.Backends; -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; -using SixLabors.ImageSharp.Drawing.Text; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Text; - -/// -/// Allows the rendering of rich text configured via . -/// -internal sealed partial class RichTextGlyphRenderer : BaseGlyphBuilder, IDisposable -{ - private const byte RenderOrderFill = 0; - private const byte RenderOrderOutline = 1; - private const byte RenderOrderDecoration = 2; - - private readonly DrawingOptions drawingOptions; - private readonly MemoryAllocator memoryAllocator; - private readonly IDrawingBackend drawingBackend; - private readonly Pen? defaultPen; - private readonly Brush? defaultBrush; - private readonly IPathInternals? path; - private bool isDisposed; - - private TextRun? currentTextRun; - private Brush? currentBrush; - private Pen? currentPen; - private FillRule currentFillRule; - private PixelAlphaCompositionMode currentCompositionMode; - private PixelColorBlendingMode currentBlendingMode; - private bool currentDecorationIsVertical; - private bool hasLayer; - - // Just enough accuracy to allow for 1/8 px differences which later are accumulated while rendering, - // but do not grow into full px offsets. - // The value 8 is benchmarked to: - // - Provide a good accuracy (smaller than 0.2% image difference compared to the non-caching variant) - // - Cache hit ratio above 60% - private const float AccuracyMultiple = 8; - private readonly Dictionary> glyphCache = []; - private int cacheReadIndex; - - private bool rasterizationRequired; - private readonly bool noCache; - private CacheKey currentCacheKey; - - public RichTextGlyphRenderer( - RichTextOptions textOptions, - DrawingOptions drawingOptions, - MemoryAllocator memoryAllocator, - IDrawingBackend drawingBackend, - Pen? pen, - Brush? brush) - : base(drawingOptions.Transform) - { - this.drawingOptions = drawingOptions; - this.memoryAllocator = memoryAllocator; - this.drawingBackend = drawingBackend; - this.defaultPen = pen; - this.defaultBrush = brush; - this.DrawingOperations = []; - this.currentCompositionMode = drawingOptions.GraphicsOptions.AlphaCompositionMode; - this.currentBlendingMode = drawingOptions.GraphicsOptions.ColorBlendingMode; - - IPath? path = textOptions.Path; - if (path is not null) - { - // Turn off caching. The chances of a hit are near-zero. - this.rasterizationRequired = true; - this.noCache = true; - if (path is IPathInternals internals) - { - this.path = internals; - } - else - { - this.path = new ComplexPolygon(path); - } - } - } - - public List DrawingOperations { get; } - - /// - protected override void BeginText(in FontRectangle bounds) - { - foreach (DrawingOperation operation in this.DrawingOperations) - { - operation.Map.Dispose(); - } - - this.DrawingOperations.Clear(); - } - - /// - protected override void BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) - { - // Reset state. - this.cacheReadIndex = 0; - this.currentDecorationIsVertical = parameters.LayoutMode is GlyphLayoutMode.Vertical or GlyphLayoutMode.VerticalRotated; - this.currentTextRun = parameters.TextRun; - if (parameters.TextRun is RichTextRun drawingRun) - { - this.currentBrush = drawingRun.Brush; - this.currentPen = drawingRun.Pen; - } - else - { - this.currentBrush = null; - this.currentPen = null; - } - - if (!this.noCache) - { - // Create a cache entry for the glyph. - // We need to apply the default transform to the bounds to get the correct size - // for comparison with future glyphs. We can use this cached glyph anywhere in the text block. - RectangleF currentBounds = RectangleF.Transform( - new RectangleF(bounds.Location, new SizeF(bounds.Width, bounds.Height)), - this.drawingOptions.Transform); - - PointF currentBoundsDelta = currentBounds.Location - ClampToPixel(currentBounds.Location); - PointF subPixelLocation = new( - MathF.Round(currentBoundsDelta.X * AccuracyMultiple) / AccuracyMultiple, - MathF.Round(currentBoundsDelta.Y * AccuracyMultiple) / AccuracyMultiple); - - SizeF subPixelSize = new( - MathF.Round(currentBounds.Width * AccuracyMultiple) / AccuracyMultiple, - MathF.Round(currentBounds.Height * AccuracyMultiple) / AccuracyMultiple); - - this.currentCacheKey = CacheKey.FromParameters(parameters, new RectangleF(subPixelLocation, subPixelSize)); - if (this.glyphCache.ContainsKey(this.currentCacheKey)) - { - // We have already drawn the glyph vectors. - this.rasterizationRequired = false; - return; - } - } - - // Transform the glyph vectors using the original bounds - // The default transform will automatically be applied. - this.TransformGlyph(in bounds); - this.rasterizationRequired = true; - } - - protected override void BeginLayer(Paint? paint, FillRule fillRule, ClipQuad? clipBounds) - { - this.hasLayer = true; - if (TryCreateBrush(paint, this.Builder.Transform, out Brush? brush)) - { - this.currentBrush = brush; - this.currentCompositionMode = TextUtilities.MapCompositionMode(paint.CompositeMode); - this.currentBlendingMode = TextUtilities.MapBlendingMode(paint.CompositeMode); - } - } - - protected override void EndLayer() - { - GlyphRenderData renderData = default; - - // Fix up the text runs colors. - // Only if both brush and pen is null do we fallback to the default value. - if (this.currentBrush == null && this.currentPen == null) - { - this.currentBrush = this.defaultBrush; - this.currentPen = this.defaultPen; - } - - // When rendering layers we only fill them. - // Any drawing of outlines is ignored as that doesn't really make sense. - bool renderFill = this.currentBrush != null; - - // Path has already been added to the collection via the base class. - IPath path = this.CurrentPaths[^1]; - Point renderLocation = ClampToPixel(path.Bounds.Location); - if (this.noCache || this.rasterizationRequired) - { - if (path.Bounds.Equals(RectangleF.Empty)) - { - return; - } - - if (renderFill) - { - renderData.FillMap = this.Render(path); - } - - // Capture the delta between the location and the truncated render location. - // We can use this to offset the render location on the next instance of this glyph. - renderData.LocationDelta = (Vector2)(path.Bounds.Location - renderLocation); - - if (!this.noCache) - { - this.UpdateCache(renderData); - } - } - else - { - renderData = this.glyphCache[this.currentCacheKey][this.cacheReadIndex++]; - - // Offset the render location by the delta from the cached glyph and this one. - Vector2 previousDelta = renderData.LocationDelta; - Vector2 currentLocation = path.Bounds.Location; - Vector2 currentDelta = path.Bounds.Location - ClampToPixel(path.Bounds.Location); - - if (previousDelta.Y > currentDelta.Y) - { - // Move the location down to match the previous location offset. - currentLocation += new Vector2(0, previousDelta.Y - currentDelta.Y); - } - else if (previousDelta.Y < currentDelta.Y) - { - // Move the location up to match the previous location offset. - currentLocation -= new Vector2(0, currentDelta.Y - previousDelta.Y); - } - else if (previousDelta.X > currentDelta.X) - { - // Move the location right to match the previous location offset. - currentLocation += new Vector2(previousDelta.X - currentDelta.X, 0); - } - else if (previousDelta.X < currentDelta.X) - { - // Move the location left to match the previous location offset. - currentLocation -= new Vector2(currentDelta.X - previousDelta.X, 0); - } - - renderLocation = ClampToPixel(currentLocation); - } - - if (renderData.FillMap != null) - { - this.DrawingOperations.Add(new DrawingOperation - { - RenderLocation = renderLocation, - Map = renderData.FillMap, - Brush = this.currentBrush!, - RenderPass = RenderOrderFill, - PixelAlphaCompositionMode = this.currentCompositionMode, - PixelColorBlendingMode = this.currentBlendingMode - }); - } - - this.currentFillRule = FillRule.NonZero; - this.currentCompositionMode = this.drawingOptions.GraphicsOptions.AlphaCompositionMode; - this.currentBlendingMode = this.drawingOptions.GraphicsOptions.ColorBlendingMode; - } - - public override TextDecorations EnabledDecorations() - { - TextRun? run = this.currentTextRun; - TextDecorations decorations = run?.TextDecorations ?? TextDecorations.None; - - if (this.currentTextRun is RichTextRun drawingRun) - { - if (drawingRun.UnderlinePen != null) - { - decorations |= TextDecorations.Underline; - } - - if (drawingRun.StrikeoutPen != null) - { - decorations |= TextDecorations.Strikeout; - } - - if (drawingRun.OverlinePen != null) - { - decorations |= TextDecorations.Overline; - } - } - - return decorations; - } - - public override void SetDecoration(TextDecorations textDecorations, Vector2 start, Vector2 end, float thickness) - { - if (thickness == 0) - { - return; - } - - Brush? brush = null; - Pen? pen = null; - if (this.currentTextRun is RichTextRun drawingRun) - { - brush = drawingRun.Brush; - - if (textDecorations == TextDecorations.Strikeout) - { - pen = drawingRun.StrikeoutPen ?? pen; - } - else if (textDecorations == TextDecorations.Underline) - { - pen = drawingRun.UnderlinePen ?? pen; - } - else if (textDecorations == TextDecorations.Overline) - { - pen = drawingRun.OverlinePen; - } - } - - // Always respect the pen stroke width if explicitly set. - float originalThickness = thickness; - if (pen is not null) - { - // Clamp the thickness to whole pixels. - thickness = MathF.Max(1F, (float)Math.Round(pen.StrokeWidth)); - } - else - { - // The thickness of the line has already been clamped in the base class. - pen = new SolidPen((brush ?? this.defaultBrush)!, thickness); - } - - // Path has already been added to the collection via the base class. - IPath path = this.CurrentPaths[^1]; - IPath outline = path; - - if (originalThickness != thickness) - { - // Respect edge anchoring per decoration type: - // - Overline: keep the base edge fixed (bottom in horizontal; left in vertical) - // - Underline: keep the top edge fixed (top in horizontal; right in vertical) - // - Strikeout: keep the center fixed (default behavior) - float ratio = thickness / originalThickness; - if (ratio != 1f) - { - Vector2 scale = this.currentDecorationIsVertical - ? new Vector2(ratio, 1f) - : new Vector2(1f, ratio); - - RectangleF b = path.Bounds; - Vector2 center = new(b.Left + (b.Width * 0.5f), b.Top + (b.Height * 0.5f)); - Vector2 anchor = center; - - if (textDecorations == TextDecorations.Overline) - { - anchor = this.currentDecorationIsVertical - ? new Vector2(b.Left, center.Y) // vertical: anchor left edge - : new Vector2(center.X, b.Bottom); // horizontal: anchor bottom edge - } - else if (textDecorations == TextDecorations.Underline) - { - anchor = this.currentDecorationIsVertical - ? new Vector2(b.Right, center.Y) // vertical: anchor right edge - : new Vector2(center.X, b.Top); // horizontal: anchor top edge - } - - // Scale about the chosen anchor so the fixed edge stays in place. - outline = outline.Transform(Matrix3x2.CreateScale(scale, anchor)); - } - } - - // Render the path here. Decorations are un-cached. - this.DrawingOperations.Add(new DrawingOperation - { - Brush = pen.StrokeFill, - RenderLocation = ClampToPixel(outline.Bounds.Location), - Map = this.Render(outline), - RenderPass = RenderOrderDecoration - }); - } - - protected override void EndGlyph() - { - if (this.hasLayer) - { - // The layer has already been rendered. - this.hasLayer = false; - return; - } - - GlyphRenderData renderData = default; - - // Fix up the text runs colors. - // Only if both brush and pen is null do we fallback to the default value. - if (this.currentBrush == null && this.currentPen == null) - { - this.currentBrush = this.defaultBrush; - this.currentPen = this.defaultPen; - } - - bool renderFill = false; - bool renderOutline = false; - - // If we are using the fonts color layers we ignore the request to draw an outline only - // because that won't really work. Instead we force drawing using fill with the requested color. - if (this.currentBrush != null) - { - renderFill = true; - } - - if (this.currentPen != null) - { - renderOutline = true; - } - - // Path has already been added to the collection via the base class. - IPath path = this.CurrentPaths[^1]; - Point renderLocation = ClampToPixel(path.Bounds.Location); - if (this.noCache || this.rasterizationRequired) - { - if (path.Bounds.Equals(RectangleF.Empty)) - { - return; - } - - if (renderFill) - { - renderData.FillMap = this.Render(path); - } - - // Capture the delta between the location and the truncated render location. - // We can use this to offset the render location on the next instance of this glyph. - renderData.LocationDelta = (Vector2)(path.Bounds.Location - renderLocation); - - if (renderOutline) - { - path = this.currentPen!.GeneratePath(path); - renderData.OutlineMap = this.Render(path); - } - - if (!this.noCache) - { - this.UpdateCache(renderData); - } - } - else - { - renderData = this.glyphCache[this.currentCacheKey][this.cacheReadIndex++]; - - // Offset the render location by the delta from the cached glyph and this one. - Vector2 previousDelta = renderData.LocationDelta; - Vector2 currentLocation = path.Bounds.Location; - Vector2 currentDelta = path.Bounds.Location - ClampToPixel(path.Bounds.Location); - - if (previousDelta.Y > currentDelta.Y) - { - // Move the location down to match the previous location offset. - currentLocation += new Vector2(0, previousDelta.Y - currentDelta.Y); - } - else if (previousDelta.Y < currentDelta.Y) - { - // Move the location up to match the previous location offset. - currentLocation -= new Vector2(0, currentDelta.Y - previousDelta.Y); - } - else if (previousDelta.X > currentDelta.X) - { - // Move the location right to match the previous location offset. - currentLocation += new Vector2(previousDelta.X - currentDelta.X, 0); - } - else if (previousDelta.X < currentDelta.X) - { - // Move the location left to match the previous location offset. - currentLocation -= new Vector2(currentDelta.X - previousDelta.X, 0); - } - - renderLocation = ClampToPixel(currentLocation); - } - - if (renderData.FillMap != null) - { - this.DrawingOperations.Add(new DrawingOperation - { - RenderLocation = renderLocation, - Map = renderData.FillMap, - Brush = this.currentBrush!, - RenderPass = RenderOrderFill, - PixelAlphaCompositionMode = this.currentCompositionMode, - PixelColorBlendingMode = this.currentBlendingMode - }); - } - - if (renderData.OutlineMap != null) - { - int offset = (int)((this.currentPen?.StrokeWidth ?? 0) / 2); - this.DrawingOperations.Add(new DrawingOperation - { - RenderLocation = renderLocation - new Size(offset, offset), - Map = renderData.OutlineMap, - Brush = this.currentPen?.StrokeFill ?? this.currentBrush!, - RenderPass = RenderOrderOutline, - PixelAlphaCompositionMode = this.currentCompositionMode, - PixelColorBlendingMode = this.currentBlendingMode - }); - } - } - - private void UpdateCache(GlyphRenderData renderData) - { - if (!this.glyphCache.TryGetValue(this.currentCacheKey, out List? _)) - { - this.glyphCache[this.currentCacheKey] = []; - } - - this.glyphCache[this.currentCacheKey].Add(renderData); - } - - public void Dispose() => this.Dispose(true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Point ClampToPixel(PointF point) => Point.Truncate(point); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void TransformGlyph(in FontRectangle bounds) - => this.Builder.SetTransform(this.ComputeTransform(in bounds)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Matrix3x2 ComputeTransform(in FontRectangle bounds) - { - if (this.path is null) - { - return Matrix3x2.Identity; - } - - // Find the point of this intersection along the given path. - // We want to find the point on the path that is closest to the center-bottom side of the glyph. - Vector2 half = new(bounds.Width * .5F, 0); - SegmentInfo pathPoint = this.path.PointAlongPath(bounds.Left + half.X); - - // Now offset to our target point since we're aligning the top-left location of our glyph against the path. - Vector2 translation = (Vector2)pathPoint.Point - bounds.Location - half + new Vector2(0, bounds.Top); - return Matrix3x2.CreateTranslation(translation) * Matrix3x2.CreateRotation(pathPoint.Angle - MathF.PI, (Vector2)pathPoint.Point); - } - - /// - /// Rasterizes a glyph path to a local coverage map. - /// - /// The glyph path in destination coordinates. - /// A coverage buffer used by later text draw operations. - private Buffer2D Render(IPath path) - { - // We need to offset the path now by the difference between the clamped location and the - // path location. - IPath offsetPath = path.Translate(-ClampToPixel(path.Bounds.Location)); - Size size = Rectangle.Ceiling(offsetPath.Bounds).Size; - - // Pad to prevent edge clipping. - size += new Size(2, 2); - - RasterizerSamplingOrigin samplingOrigin = RasterizerSamplingOrigin.PixelBoundary; - GraphicsOptions graphicsOptions = this.drawingOptions.GraphicsOptions; - RasterizationMode rasterizationMode = graphicsOptions.Antialias - ? RasterizationMode.Antialiased - : RasterizationMode.Aliased; - - // Take the path inside the path builder, scan thing and generate a Buffer2D representing the glyph. - Buffer2D buffer = this.memoryAllocator.Allocate2D(size.Width, size.Height, AllocationOptions.Clean); - RasterizerOptions rasterizerOptions = new( - new Rectangle(0, 0, size.Width, size.Height), - TextUtilities.MapFillRule(this.currentFillRule), - rasterizationMode, - samplingOrigin); - - // Request coverage generation from the configured backend. CPU backends will produce - // this via scanlines; future GPU backends can supply equivalent coverage by other means. - this.drawingBackend.RasterizeCoverage( - offsetPath, - rasterizerOptions, - this.memoryAllocator, - buffer); - - return buffer; - } - - private void Dispose(bool disposing) - { - if (!this.isDisposed) - { - if (disposing) - { - foreach (KeyValuePair> kv in this.glyphCache) - { - foreach (GlyphRenderData data in kv.Value) - { - data.Dispose(); - } - } - - this.glyphCache.Clear(); - - foreach (DrawingOperation operation in this.DrawingOperations) - { - operation.Map.Dispose(); - } - - this.DrawingOperations.Clear(); - } - - this.isDisposed = true; - } - } - - private struct GlyphRenderData : IDisposable - { - public Vector2 LocationDelta; - - public Buffer2D FillMap; - - public Buffer2D OutlineMap; - - public readonly void Dispose() - { - this.FillMap?.Dispose(); - this.OutlineMap?.Dispose(); - } - } - - private readonly struct CacheKey : IEquatable - { - public string Font { get; init; } - - public GlyphColor GlyphColor { get; init; } - - public GlyphType GlyphType { get; init; } - - public FontStyle FontStyle { get; init; } - - public ushort GlyphId { get; init; } - - public ushort CompositeGlyphId { get; init; } - - public CodePoint CodePoint { get; init; } - - public float PointSize { get; init; } - - public float Dpi { get; init; } - - public GlyphLayoutMode LayoutMode { get; init; } - - public TextAttributes TextAttributes { get; init; } - - public TextDecorations TextDecorations { get; init; } - - public RectangleF Bounds { get; init; } - - public static bool operator ==(CacheKey left, CacheKey right) => left.Equals(right); - - public static bool operator !=(CacheKey left, CacheKey right) => !(left == right); - - public static CacheKey FromParameters(in GlyphRendererParameters parameters, RectangleF bounds) - => new() - { - // Do not include the grapheme index as that will - // always vary per glyph instance. - Font = parameters.Font, - GlyphType = parameters.GlyphType, - FontStyle = parameters.FontStyle, - GlyphId = parameters.GlyphId, - CompositeGlyphId = parameters.CompositeGlyphId, - CodePoint = parameters.CodePoint, - PointSize = parameters.PointSize, - Dpi = parameters.Dpi, - LayoutMode = parameters.LayoutMode, - TextAttributes = parameters.TextRun.TextAttributes, - TextDecorations = parameters.TextRun.TextDecorations, - Bounds = bounds - }; - - public override bool Equals(object? obj) - => obj is CacheKey key && this.Equals(key); - - public bool Equals(CacheKey other) - => this.Font == other.Font && - this.GlyphColor.Equals(other.GlyphColor) && - this.GlyphType == other.GlyphType && - this.FontStyle == other.FontStyle && - this.GlyphId == other.GlyphId && - this.CompositeGlyphId == other.CompositeGlyphId && - this.CodePoint.Equals(other.CodePoint) && - this.PointSize == other.PointSize && - this.Dpi == other.Dpi && - this.LayoutMode == other.LayoutMode && - this.TextAttributes == other.TextAttributes && - this.TextDecorations == other.TextDecorations && - this.Bounds.Equals(other.Bounds); - - public override int GetHashCode() - { - HashCode hash = default; - hash.Add(this.Font); - hash.Add(this.GlyphColor); - hash.Add(this.GlyphType); - hash.Add(this.FontStyle); - hash.Add(this.GlyphId); - hash.Add(this.CompositeGlyphId); - hash.Add(this.CodePoint); - hash.Add(this.PointSize); - hash.Add(this.Dpi); - hash.Add(this.LayoutMode); - hash.Add(this.TextAttributes); - hash.Add(this.TextDecorations); - hash.Add(this.Bounds); - return hash.ToHashCode(); - } - } -} diff --git a/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs b/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs index 6e13391bb..e83aacf3c 100644 --- a/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Drawing.Processing; /// @@ -11,11 +15,6 @@ namespace SixLabors.ImageSharp.Drawing.Processing; /// public sealed class RadialGradientBrush : GradientBrush { - private readonly PointF center0; - private readonly float radius0; - private readonly PointF? center1; // null means single-circle form - private readonly float? radius1; - /// /// Initializes a new instance of the class using a single circle. /// @@ -30,10 +29,10 @@ public RadialGradientBrush( params ColorStop[] colorStops) : base(repetitionMode, colorStops) { - this.center0 = center; - this.radius0 = radius; - this.center1 = null; - this.radius1 = null; + this.Center0 = center; + this.Radius0 = radius; + this.Center1 = null; + this.Radius1 = null; } /// @@ -54,10 +53,49 @@ public RadialGradientBrush( params ColorStop[] colorStops) : base(repetitionMode, colorStops) { - this.center0 = startCenter; - this.radius0 = startRadius; - this.center1 = endCenter; - this.radius1 = endRadius; + this.Center0 = startCenter; + this.Radius0 = startRadius; + this.Center1 = endCenter; + this.Radius1 = endRadius; + } + + /// + /// Gets the center of the starting circle. + /// + public PointF Center0 { get; } + + /// + /// Gets the radius of the starting circle. + /// + public float Radius0 { get; } + + /// + /// Gets the center of the ending circle, or for single-circle form. + /// + public PointF? Center1 { get; } + + /// + /// Gets the radius of the ending circle, or for single-circle form. + /// + public float? Radius1 { get; } + + /// + /// Gets a value indicating whether this is a two-circle radial gradient. + /// + public bool IsTwoCircle => this.Center1.HasValue && this.Radius1.HasValue; + + /// + public override Brush Transform(Matrix4x4 matrix) + { + PointF tc0 = PointF.Transform(this.Center0, matrix); + float scale = MatrixUtilities.GetAverageScale(in matrix); + if (this.IsTwoCircle) + { + PointF tc1 = PointF.Transform(this.Center1!.Value, matrix); + return new RadialGradientBrush(tc0, this.Radius0 * scale, tc1, this.Radius1!.Value * scale, this.RepetitionMode, this.ColorStopsArray); + } + + return new RadialGradientBrush(tc0, this.Radius0 * scale, this.RepetitionMode, this.ColorStopsArray); } /// @@ -66,10 +104,10 @@ public override bool Equals(Brush? other) if (other is RadialGradientBrush b) { return base.Equals(other) - && this.center0.Equals(b.center0) - && this.radius0.Equals(b.radius0) - && Nullable.Equals(this.center1, b.center1) - && Nullable.Equals(this.radius1, b.radius1); + && this.Center0.Equals(b.Center0) + && this.Radius0.Equals(b.Radius0) + && Nullable.Equals(this.Center1, b.Center1) + && Nullable.Equals(this.Radius1, b.Radius1); } return false; @@ -77,23 +115,23 @@ public override bool Equals(Brush? other) /// public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), this.center0, this.radius0, this.center1, this.radius1); + => HashCode.Combine(base.GetHashCode(), this.Center0, this.Radius0, this.Center1, this.Radius1); /// public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) => new RadialGradientBrushApplicator( configuration, options, - source, - this.center0, - this.radius0, - this.center1, - this.radius1, - this.ColorStops, + targetRegion, + this.Center0, + this.Radius0, + this.Center1, + this.Radius1, + this.ColorStopsArray, this.RepetitionMode); /// @@ -125,7 +163,7 @@ private sealed class RadialGradientBrushApplicator : GradientBrushApplic /// /// The configuration instance to use when performing operations. /// The graphics options. - /// The target image. + /// The destination pixel region. /// Center of the starting circle. /// Radius of the starting circle. /// Center of the ending circle, or null to use single-circle form. @@ -135,14 +173,14 @@ private sealed class RadialGradientBrushApplicator : GradientBrushApplic public RadialGradientBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame target, + Buffer2DRegion targetRegion, PointF center0, float radius0, PointF? center1, float? radius1, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) - : base(configuration, options, target, colorStops, repetitionMode) + : base(configuration, options, targetRegion, colorStops, repetitionMode) { this.c0x = center0.X; this.c0y = center0.Y; diff --git a/src/ImageSharp.Drawing/Processing/RasterizerDefaultsExtensions.cs b/src/ImageSharp.Drawing/Processing/RasterizerDefaultsExtensions.cs index db2361cfc..c600a40de 100644 --- a/src/ImageSharp.Drawing/Processing/RasterizerDefaultsExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/RasterizerDefaultsExtensions.cs @@ -2,14 +2,13 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Drawing.Processing.Backends; -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; namespace SixLabors.ImageSharp.Drawing.Processing; /// -/// Adds extensions that allow configuring the path rasterizer implementation. +/// Adds extensions that allow configuring the drawing backend implementation. /// -internal static class RasterizerDefaultsExtensions +public static class RasterizerDefaultsExtensions { /// /// Sets the drawing backend against the source image processing context. @@ -22,11 +21,6 @@ internal static IImageProcessingContext SetDrawingBackend(this IImageProcessingC Guard.NotNull(backend, nameof(backend)); context.Properties[typeof(IDrawingBackend)] = backend; - if (backend is CpuDrawingBackend cpuBackend) - { - context.Properties[typeof(IRasterizer)] = cpuBackend.PrimaryRasterizer; - } - return context; } @@ -35,15 +29,10 @@ internal static IImageProcessingContext SetDrawingBackend(this IImageProcessingC /// /// The configuration to store the backend against. /// The backend to use. - internal static void SetDrawingBackend(this Configuration configuration, IDrawingBackend backend) + public static void SetDrawingBackend(this Configuration configuration, IDrawingBackend backend) { Guard.NotNull(backend, nameof(backend)); configuration.Properties[typeof(IDrawingBackend)] = backend; - - if (backend is CpuDrawingBackend cpuBackend) - { - configuration.Properties[typeof(IRasterizer)] = cpuBackend.PrimaryRasterizer; - } } /// @@ -54,17 +43,12 @@ internal static void SetDrawingBackend(this Configuration configuration, IDrawin internal static IDrawingBackend GetDrawingBackend(this IImageProcessingContext context) { if (context.Properties.TryGetValue(typeof(IDrawingBackend), out object? backend) && - backend is IDrawingBackend configured) + backend is IDrawingBackend configured && + configured.IsSupported) { return configured; } - if (context.Properties.TryGetValue(typeof(IRasterizer), out object? rasterizer) && - rasterizer is IRasterizer configuredRasterizer) - { - return CpuDrawingBackend.Create(configuredRasterizer); - } - return context.Configuration.GetDrawingBackend(); } @@ -76,95 +60,14 @@ internal static IDrawingBackend GetDrawingBackend(this IImageProcessingContext c internal static IDrawingBackend GetDrawingBackend(this Configuration configuration) { if (configuration.Properties.TryGetValue(typeof(IDrawingBackend), out object? backend) && - backend is IDrawingBackend configured) + backend is IDrawingBackend configured && + configured.IsSupported) { return configured; } - if (configuration.Properties.TryGetValue(typeof(IRasterizer), out object? rasterizer) && - rasterizer is IRasterizer configuredRasterizer) - { - IDrawingBackend rasterizerBackend = CpuDrawingBackend.Create(configuredRasterizer); - configuration.Properties[typeof(IDrawingBackend)] = rasterizerBackend; - return rasterizerBackend; - } - - IDrawingBackend defaultBackend = CpuDrawingBackend.Instance; + IDrawingBackend defaultBackend = DefaultDrawingBackend.Instance; configuration.Properties[typeof(IDrawingBackend)] = defaultBackend; return defaultBackend; } - - /// - /// Sets the rasterizer against the source image processing context. - /// - /// The image processing context to store the rasterizer against. - /// The rasterizer to use. - /// The passed in to allow chaining. - internal static IImageProcessingContext SetRasterizer(this IImageProcessingContext context, IRasterizer rasterizer) - { - Guard.NotNull(rasterizer, nameof(rasterizer)); - context.Properties[typeof(IRasterizer)] = rasterizer; - context.Properties[typeof(IDrawingBackend)] = CpuDrawingBackend.Create(rasterizer); - return context; - } - - /// - /// Sets the default rasterizer against the configuration. - /// - /// The configuration to store the rasterizer against. - /// The rasterizer to use. - internal static void SetRasterizer(this Configuration configuration, IRasterizer rasterizer) - { - Guard.NotNull(rasterizer, nameof(rasterizer)); - configuration.Properties[typeof(IRasterizer)] = rasterizer; - configuration.Properties[typeof(IDrawingBackend)] = CpuDrawingBackend.Create(rasterizer); - } - - /// - /// Gets the rasterizer from the source image processing context. - /// - /// The image processing context to retrieve the rasterizer from. - /// The configured rasterizer. - internal static IRasterizer GetRasterizer(this IImageProcessingContext context) - { - if (context.Properties.TryGetValue(typeof(IRasterizer), out object? rasterizer) && - rasterizer is IRasterizer configured) - { - return configured; - } - - if (context.Properties.TryGetValue(typeof(IDrawingBackend), out object? backend) && - backend is CpuDrawingBackend cpuBackend) - { - return cpuBackend.PrimaryRasterizer; - } - - // Do not cache config fallback in the context so changes on configuration reflow. - return context.Configuration.GetRasterizer(); - } - - /// - /// Gets the default rasterizer from the configuration. - /// - /// The configuration to retrieve the rasterizer from. - /// The configured rasterizer. - internal static IRasterizer GetRasterizer(this Configuration configuration) - { - if (configuration.Properties.TryGetValue(typeof(IRasterizer), out object? rasterizer) && - rasterizer is IRasterizer configured) - { - return configured; - } - - if (configuration.Properties.TryGetValue(typeof(IDrawingBackend), out object? backend) && - backend is CpuDrawingBackend cpuBackend) - { - return cpuBackend.PrimaryRasterizer; - } - - IRasterizer defaultRasterizer = DefaultRasterizer.Instance; - configuration.Properties[typeof(IRasterizer)] = defaultRasterizer; - configuration.Properties[typeof(IDrawingBackend)] = CpuDrawingBackend.Instance; - return defaultRasterizer; - } } diff --git a/src/ImageSharp.Drawing/Processing/RecolorBrush.cs b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs index c9369761e..a4fb37a2e 100644 --- a/src/ImageSharp.Drawing/Processing/RecolorBrush.cs +++ b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs @@ -2,7 +2,8 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using SixLabors.ImageSharp.Drawing.Utilities; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Drawing.Processing; @@ -43,11 +44,11 @@ public RecolorBrush(Color sourceColor, Color targetColor, float threshold) public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) => new RecolorBrushApplicator( configuration, options, - source, + targetRegion, this.SourceColor.ToPixel(), this.TargetColor.ToPixel(), this.Threshold); @@ -87,18 +88,18 @@ private class RecolorBrushApplicator : BrushApplicator /// /// The configuration instance to use when performing operations. /// The options - /// The source image. + /// The destination pixel region. /// Color of the source. /// Color of the target. /// The threshold . public RecolorBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, TPixel sourceColor, TPixel targetColor, float threshold) - : base(configuration, options, source) + : base(configuration, options, targetRegion) { this.sourceColor = sourceColor.ToScaledVector4(); this.targetColorPixel = targetColor; @@ -108,7 +109,7 @@ public RecolorBrushApplicator( TPixel maxColor = TPixel.FromVector4(new Vector4(float.MaxValue)); TPixel minColor = TPixel.FromVector4(new Vector4(float.MinValue)); this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; - this.blenderBuffers = new ThreadLocalBlenderBuffers(configuration.MemoryAllocator, source.Width); + this.blenderBuffers = new ThreadLocalBlenderBuffers(configuration.MemoryAllocator, targetRegion.Width); } internal TPixel this[int x, int y] @@ -116,7 +117,9 @@ public RecolorBrushApplicator( get { // Offset the requested pixel by the value in the rectangle (the shapes position) - TPixel result = this.Target[x, y]; + int localY = y - this.TargetRegion.Rectangle.Y; + int localX = x - this.TargetRegion.Rectangle.X; + TPixel result = this.TargetRegion.DangerousGetRowSpan(localY)[localX]; Vector4 background = result.ToVector4(); float distance = Vector4.DistanceSquared(background, this.sourceColor); if (distance <= this.threshold) @@ -135,28 +138,38 @@ public RecolorBrushApplicator( /// public override void Apply(Span scanline, int x, int y) { - if (x < 0 || y < 0 || x >= this.Target.Width || y >= this.Target.Height) + Rectangle targetBounds = this.TargetRegion.Rectangle; + if (y < targetBounds.Y || y >= targetBounds.Bottom) { return; } - // Limit the scanline to the bounds of the image relative to x. - scanline = scanline[..Math.Min(this.Target.Width - x, scanline.Length)]; - Span amounts = this.blenderBuffers.AmountSpan[..scanline.Length]; - Span overlays = this.blenderBuffers.OverlaySpan[..scanline.Length]; + int startX = Math.Max(x, targetBounds.X); + int endX = Math.Min(x + scanline.Length, targetBounds.Right); + if (startX >= endX) + { + return; + } + + int length = endX - startX; + Span clippedScanline = scanline.Slice(startX - x, length); + Span amounts = this.blenderBuffers.AmountSpan[..length]; + Span overlays = this.blenderBuffers.OverlaySpan[..length]; - for (int i = 0; i < scanline.Length; i++) + for (int i = 0; i < clippedScanline.Length; i++) { - amounts[i] = scanline[i] * this.Options.BlendPercentage; + amounts[i] = clippedScanline[i] * this.Options.BlendPercentage; - int offsetX = x + i; + int offsetX = startX + i; // No doubt this one can be optimized further but I can't imagine its // actually being used and can probably be removed/internalized for now overlays[i] = this[offsetX, y]; } - Span destinationRow = this.Target.PixelBuffer.DangerousGetRowSpan(y).Slice(x, scanline.Length); + int localY = y - targetBounds.Y; + int localX = startX - targetBounds.X; + Span destinationRow = this.TargetRegion.DangerousGetRowSpan(localY).Slice(localX, length); this.Blender.Blend( this.Configuration, destinationRow, diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.Brushes.cs b/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.Brushes.cs similarity index 88% rename from src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.Brushes.cs rename to src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.Brushes.cs index f9e044838..c35c40856 100644 --- a/src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.Brushes.cs +++ b/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.Brushes.cs @@ -5,6 +5,7 @@ using System.Numerics; using SixLabors.Fonts; using SixLabors.Fonts.Rendering; +using SixLabors.ImageSharp.Drawing.Helpers; namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Text; @@ -20,7 +21,7 @@ internal sealed partial class RichTextGlyphRenderer /// A transform to apply to the brush coordinates. /// The resulting brush, or if the paint is unsupported. /// if a brush could be created; otherwise, . - public static bool TryCreateBrush([NotNullWhen(true)] Paint? paint, Matrix3x2 transform, [NotNullWhen(true)] out Brush? brush) + public static bool TryCreateBrush([NotNullWhen(true)] Paint? paint, Matrix4x4 transform, [NotNullWhen(true)] out Brush? brush) { brush = null; @@ -53,7 +54,7 @@ public static bool TryCreateBrush([NotNullWhen(true)] Paint? paint, Matrix3x2 tr /// The transform to apply to the gradient points. /// The resulting brush. /// if created; otherwise, . - private static bool TryCreateLinearGradientBrush(LinearGradientPaint paint, Matrix3x2 transform, out Brush? brush) + private static bool TryCreateLinearGradientBrush(LinearGradientPaint paint, Matrix4x4 transform, out Brush? brush) { // Map gradient stops (apply paint opacity multiplier to each stop's alpha). ColorStop[] stops = ToColorStops(paint.Stops, paint.Opacity); @@ -68,12 +69,12 @@ private static bool TryCreateLinearGradientBrush(LinearGradientPaint paint, Matr // Apply any transform defined on the paint. if (!transform.IsIdentity) { - p0 = Vector2.Transform(p0, transform); - p1 = Vector2.Transform(p1, transform); + p0 = PointF.Transform(p0, transform); + p1 = PointF.Transform(p1, transform); if (p2.HasValue) { - p2 = Vector2.Transform(p2.Value, transform); + p2 = PointF.Transform(p2.Value, transform); } } @@ -94,7 +95,7 @@ private static bool TryCreateLinearGradientBrush(LinearGradientPaint paint, Matr /// The transform to apply to the gradient center point. /// The resulting brush. /// if created; otherwise, . - private static bool TryCreateRadialGradientBrush(RadialGradientPaint paint, Matrix3x2 transform, out Brush? brush) + private static bool TryCreateRadialGradientBrush(RadialGradientPaint paint, Matrix4x4 transform, out Brush? brush) { // Map gradient stops (apply paint opacity multiplier to each stop's alpha). ColorStop[] stops = ToColorStops(paint.Stops, paint.Opacity); @@ -105,13 +106,18 @@ private static bool TryCreateRadialGradientBrush(RadialGradientPaint paint, Matr // Apply any transform defined on the paint. PointF center0 = paint.Center0; PointF center1 = paint.Center1; + float radius0 = paint.Radius0; + float radius1 = paint.Radius1; if (!transform.IsIdentity) { - center0 = Vector2.Transform(center0, transform); - center1 = Vector2.Transform(center1, transform); + center0 = PointF.Transform(center0, transform); + center1 = PointF.Transform(center1, transform); + float scale = MatrixUtilities.GetAverageScale(in transform); + radius0 *= scale; + radius1 *= scale; } - brush = new RadialGradientBrush(center0, paint.Radius0, center1, paint.Radius1, mode, stops); + brush = new RadialGradientBrush(center0, radius0, center1, radius1, mode, stops); return true; } @@ -122,7 +128,7 @@ private static bool TryCreateRadialGradientBrush(RadialGradientPaint paint, Matr /// The transform to apply to the gradient center point. /// The resulting brush. /// if created; otherwise, . - private static bool TryCreateSweepGradientBrush(SweepGradientPaint paint, Matrix3x2 transform, out Brush? brush) + private static bool TryCreateSweepGradientBrush(SweepGradientPaint paint, Matrix4x4 transform, out Brush? brush) { // Map gradient stops (apply paint opacity multiplier to each stop's alpha). ColorStop[] stops = ToColorStops(paint.Stops, paint.Opacity); @@ -134,7 +140,7 @@ private static bool TryCreateSweepGradientBrush(SweepGradientPaint paint, Matrix PointF center = paint.Center; if (!transform.IsIdentity) { - center = Vector2.Transform(center, transform); + center = PointF.Transform(center, transform); } brush = new SweepGradientBrush(center, paint.StartAngle, paint.EndAngle, mode, stops); diff --git a/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.cs b/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.cs new file mode 100644 index 000000000..b917811b4 --- /dev/null +++ b/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.cs @@ -0,0 +1,961 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.Fonts; +using SixLabors.Fonts.Rendering; +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Text; + +namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Text; + +/// +/// Allows the rendering of rich text configured via . +/// +internal sealed partial class RichTextGlyphRenderer : BaseGlyphBuilder, IDisposable +{ + // --- Render-pass ordering constants --- + // Within DrawTextOperations, operations are sorted first by RenderPass so that + // fills paint beneath outlines, and outlines beneath decorations. + private const byte RenderOrderFill = 0; + private const byte RenderOrderOutline = 1; + private const byte RenderOrderDecoration = 2; + + private readonly DrawingOptions drawingOptions; + + /// The default pen supplied by the caller (e.g. from DrawText(..., pen)). + private readonly Pen? defaultPen; + + /// The default brush supplied by the caller (e.g. from DrawText(..., brush)). + private readonly Brush? defaultBrush; + + /// + /// When the text is laid out along a path, this holds the path internals + /// for point-along-path queries. for normal (linear) text. + /// + private readonly IPathInternals? path; + private bool isDisposed; + + // --- Per-glyph mutable state reset in BeginGlyph --- + + /// The (or ) governing the current glyph. + private TextRun? currentTextRun; + + /// Brush resolved from the current , or . + private Brush? currentBrush; + + /// Pen resolved from the current , or . + private Pen? currentPen; + + /// The fill rule for the current color layer (COLR). + private FillRule currentFillRule; + + /// Alpha composition mode active for the current glyph/layer. + private PixelAlphaCompositionMode currentCompositionMode; + + /// Color blending mode active for the current glyph/layer. + private PixelColorBlendingMode currentBlendingMode; + + /// Whether the current glyph uses vertical layout (affects decoration orientation). + private bool currentDecorationIsVertical; + + /// Set to when is called, cleared in . + private bool hasLayer; + + // --- Glyph outline cache --- + // Glyphs that share the same CacheKey (same glyph id, sub-pixel position quantized + // to 1/AccuracyMultiple, pen reference, etc.) reuse the translated IPath from the + // first occurrence. This avoids re-building the full outline for repeated characters. + // + // AccuracyMultiple = 8 means sub-pixel positions are quantized to 1/8 px steps. + // Benchmarked to give <0.2% image difference vs. uncached, with >60% cache hit ratio. + private const float AccuracyMultiple = 8; + + /// Maps cache keys to their list of entries (one per layer). + private readonly Dictionary> glyphCache = []; + + /// Read cursor into the cached layer list for layered cache hits. + private int cacheReadIndex; + + /// + /// when the current glyph is a cache miss and its outline + /// must be fully rasterized; on a cache hit (reuse path). + /// + private bool rasterizationRequired; + + /// + /// to disable the glyph cache entirely (e.g. path-based text + /// where every glyph has a unique transform). + /// + private readonly bool noCache; + + /// The cache key computed for the current glyph in . + private CacheKey currentCacheKey; + + /// + /// The transformed (post-) bounding-box location + /// of the current glyph. Stored so can compute + /// for future cache-hit render location estimation. + /// + private PointF currentTransformedBoundsLocation; + + /// + /// Initializes a new instance of the class. + /// + /// Rich text options that may include a layout path and text runs. + /// Drawing options (transform, graphics options) for the text block. + /// Default pen for outlined text, or for fill-only. + /// Default brush for filled text, or for outline-only. + public RichTextGlyphRenderer( + RichTextOptions textOptions, + DrawingOptions drawingOptions, + Pen? pen, + Brush? brush) + : base(drawingOptions.Transform) + { + this.drawingOptions = drawingOptions; + this.defaultPen = pen; + this.defaultBrush = brush; + this.DrawingOperations = []; + this.currentCompositionMode = drawingOptions.GraphicsOptions.AlphaCompositionMode; + this.currentBlendingMode = drawingOptions.GraphicsOptions.ColorBlendingMode; + + IPath? path = textOptions.Path; + if (path is not null) + { + // Path-based text: each glyph gets a unique per-position transform, + // so cache hits are near-impossible — disable caching entirely. + this.rasterizationRequired = true; + this.noCache = true; + if (path is IPathInternals internals) + { + this.path = internals; + } + else + { + this.path = new ComplexPolygon(path); + } + } + } + + /// + /// Gets the list of instances accumulated during text rendering. + /// After RenderText completes, this list is consumed by + /// to build composition commands. + /// + public List DrawingOperations { get; } + + /// + protected override void BeginText(in FontRectangle bounds) => this.DrawingOperations.Clear(); + + /// + protected override bool BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) + { + // Resolves the active brush/pen from the text run, computes the cache key, + // and takes one of three paths: + // 1. Non-layered cache hit without decorations → emit cached ops, return false (fast path). + // 2. Layered or decorated cache hit → reuse cached path, return true for EndGlyph/SetDecoration. + // 3. Cache miss → rasterize from scratch. + this.cacheReadIndex = 0; + this.currentDecorationIsVertical = parameters.LayoutMode is GlyphLayoutMode.Vertical or GlyphLayoutMode.VerticalRotated; + this.currentTextRun = parameters.TextRun; + if (parameters.TextRun is RichTextRun drawingRun) + { + this.currentBrush = drawingRun.Brush; + this.currentPen = drawingRun.Pen; + } + else + { + this.currentBrush = null; + this.currentPen = null; + } + + if (!this.noCache) + { + // Transform the font-metric bounds by the drawing transform so that the + // sub-pixel position and size reflect the final screen coordinates. + // Quantize to 1/AccuracyMultiple px steps for cache key comparison. + RectangleF currentBounds = RectangleF.Transform( + new RectangleF(bounds.Location, new SizeF(bounds.Width, bounds.Height)), + this.drawingOptions.Transform); + + this.currentTransformedBoundsLocation = currentBounds.Location; + + PointF currentBoundsDelta = currentBounds.Location - ClampToPixel(currentBounds.Location); + PointF subPixelLocation = new( + MathF.Round(currentBoundsDelta.X * AccuracyMultiple) / AccuracyMultiple, + MathF.Round(currentBoundsDelta.Y * AccuracyMultiple) / AccuracyMultiple); + + SizeF subPixelSize = new( + MathF.Round(currentBounds.Width * AccuracyMultiple) / AccuracyMultiple, + MathF.Round(currentBounds.Height * AccuracyMultiple) / AccuracyMultiple); + + this.currentCacheKey = CacheKey.FromParameters( + parameters, + new RectangleF(subPixelLocation, subPixelSize), + this.currentPen ?? this.defaultPen); + + if (this.glyphCache.TryGetValue(this.currentCacheKey, out List? cachedEntries)) + { + if (cachedEntries.Count > 0 && !cachedEntries[0].IsLayered + && this.EnabledDecorations() == TextDecorations.None) + { + // Non-layered cache hit without decorations: emit operations directly + // and tell the font engine to skip the outline entirely + // (no MoveTo/LineTo/SetDecoration/EndGlyph). + this.EmitCachedGlyphOperations(cachedEntries[0], currentBounds.Location); + return false; + } + + // Layered or decorated cache hit: let the normal flow handle + // per-layer state and decoration callbacks. + this.rasterizationRequired = false; + return true; + } + } + + // Transform the glyph vectors using the original bounds + // The default transform will automatically be applied. + this.TransformGlyph(in bounds); + this.rasterizationRequired = true; + return true; + } + + /// + protected override void BeginLayer(Paint? paint, FillRule fillRule, ClipQuad? clipBounds) + { + // Capture the color-layer paint, fill rule, and composite mode. + // Setting hasLayer tells EndGlyph to skip its default single-layer path emission. + this.hasLayer = true; + this.currentFillRule = fillRule; + if (TryCreateBrush(paint, this.Builder.Transform, out Brush? brush)) + { + this.currentBrush = brush; + this.currentCompositionMode = TextUtilities.MapCompositionMode(paint.CompositeMode); + this.currentBlendingMode = TextUtilities.MapBlendingMode(paint.CompositeMode); + } + } + + /// + protected override void EndLayer() + { + // Finalizes a color layer. On a cache miss, translates the built path to local + // coordinates and stores it for future hits. On a cache hit, reads the stored + // path and adjusts the render location using sub-pixel delta compensation. + GlyphRenderData renderData = default; + IPath? fillPath = null; + + // Fix up the text runs colors. + // Only if both brush and pen is null do we fallback to the default value. + if (this.currentBrush == null && this.currentPen == null) + { + this.currentBrush = this.defaultBrush; + this.currentPen = this.defaultPen; + } + + // When rendering layers we only fill them. + // Any drawing of outlines is ignored as that doesn't really make sense. + bool renderFill = this.currentBrush != null; + + // Path has already been added to the collection via the base class. + IPath path = this.CurrentPaths[^1]; + Point renderLocation = ClampToPixel(path.Bounds.Location); + if (this.noCache || this.rasterizationRequired) + { + if (path.Bounds.Equals(RectangleF.Empty)) + { + return; + } + + if (renderFill) + { + renderData.FillPath = path.Translate(-renderLocation); + fillPath = renderData.FillPath; + } + + // Capture the delta between the location and the truncated render location. + // We can use this to offset the render location on the next instance of this glyph. + renderData.LocationDelta = (Vector2)(path.Bounds.Location - renderLocation); + renderData.IsLayered = true; + + if (!this.noCache) + { + this.UpdateCache(renderData); + } + } + else + { + renderData = this.glyphCache[this.currentCacheKey][this.cacheReadIndex++]; + + // Offset the render location by the delta from the cached glyph and this one. + Vector2 previousDelta = renderData.LocationDelta; + Vector2 currentLocation = path.Bounds.Location; + Vector2 currentDelta = path.Bounds.Location - ClampToPixel(path.Bounds.Location); + + if (previousDelta.Y > currentDelta.Y) + { + // Move the location down to match the previous location offset. + currentLocation += new Vector2(0, previousDelta.Y - currentDelta.Y); + } + else if (previousDelta.Y < currentDelta.Y) + { + // Move the location up to match the previous location offset. + currentLocation -= new Vector2(0, currentDelta.Y - previousDelta.Y); + } + else if (previousDelta.X > currentDelta.X) + { + // Move the location right to match the previous location offset. + currentLocation += new Vector2(previousDelta.X - currentDelta.X, 0); + } + else if (previousDelta.X < currentDelta.X) + { + // Move the location left to match the previous location offset. + currentLocation -= new Vector2(currentDelta.X - previousDelta.X, 0); + } + + renderLocation = ClampToPixel(currentLocation); + + if (renderFill && renderData.FillPath is not null) + { + fillPath = renderData.FillPath; + } + } + + if (fillPath is not null) + { + IntersectionRule fillRule = TextUtilities.MapFillRule(this.currentFillRule); + this.DrawingOperations.Add(new DrawingOperation + { + Kind = DrawingOperationKind.Fill, + Path = fillPath, + RenderLocation = renderLocation, + IntersectionRule = fillRule, + Brush = this.currentBrush, + RenderPass = RenderOrderFill, + PixelAlphaCompositionMode = this.currentCompositionMode, + PixelColorBlendingMode = this.currentBlendingMode + }); + } + + this.currentFillRule = FillRule.NonZero; + this.currentCompositionMode = this.drawingOptions.GraphicsOptions.AlphaCompositionMode; + this.currentBlendingMode = this.drawingOptions.GraphicsOptions.ColorBlendingMode; + } + + /// + public override TextDecorations EnabledDecorations() + { + // Returns the union of decorations from TextRun.TextDecorations and any + // decoration pens set on the current RichTextRun. The font engine uses + // this result to decide which SetDecoration calls to emit. + TextRun? run = this.currentTextRun; + TextDecorations decorations = run?.TextDecorations ?? TextDecorations.None; + + if (this.currentTextRun is RichTextRun drawingRun) + { + if (drawingRun.UnderlinePen != null) + { + decorations |= TextDecorations.Underline; + } + + if (drawingRun.StrikeoutPen != null) + { + decorations |= TextDecorations.Strikeout; + } + + if (drawingRun.OverlinePen != null) + { + decorations |= TextDecorations.Overline; + } + } + + return decorations; + } + + /// + public override void SetDecoration(TextDecorations textDecorations, Vector2 start, Vector2 end, float thickness) + { + // Emits a DrawingOperation for a text decoration. Resolves the decoration pen + // from the current RichTextRun, re-scales the base-class path when the pen's + // stroke width differs from the font-metric thickness, and anchors the scaling + // per decoration type (overline→bottom edge, underline→top edge, strikeout→center). + // Decorations are not cached. + if (thickness == 0) + { + return; + } + + Brush? brush = null; + Pen? pen = null; + if (this.currentTextRun is RichTextRun drawingRun) + { + brush = drawingRun.Brush; + + if (textDecorations == TextDecorations.Strikeout) + { + pen = drawingRun.StrikeoutPen ?? pen; + } + else if (textDecorations == TextDecorations.Underline) + { + pen = drawingRun.UnderlinePen ?? pen; + } + else if (textDecorations == TextDecorations.Overline) + { + pen = drawingRun.OverlinePen; + } + } + + // Always respect the pen stroke width if explicitly set. + float originalThickness = thickness; + if (pen is not null) + { + // Clamp the thickness to whole pixels. + thickness = MathF.Max(1F, (float)Math.Round(pen.StrokeWidth)); + } + else + { + // The thickness of the line has already been clamped in the base class. + pen = new SolidPen((brush ?? this.defaultBrush)!, thickness); + } + + // Path has already been added to the collection via the base class. + IPath path = this.CurrentPaths[^1]; + IPath outline = path; + + if (originalThickness != thickness) + { + // Respect edge anchoring per decoration type: + // - Overline: keep the base edge fixed (bottom in horizontal; left in vertical) + // - Underline: keep the top edge fixed (top in horizontal; right in vertical) + // - Strikeout: keep the center fixed (default behavior) + float ratio = thickness / originalThickness; + if (ratio != 1f) + { + Vector2 scale = this.currentDecorationIsVertical + ? new Vector2(ratio, 1f) + : new Vector2(1f, ratio); + + RectangleF b = path.Bounds; + Vector2 center = new(b.Left + (b.Width * 0.5f), b.Top + (b.Height * 0.5f)); + Vector2 anchor = center; + + if (textDecorations == TextDecorations.Overline) + { + anchor = this.currentDecorationIsVertical + ? new Vector2(b.Left, center.Y) // vertical: anchor left edge + : new Vector2(center.X, b.Bottom); // horizontal: anchor bottom edge + } + else if (textDecorations == TextDecorations.Underline) + { + anchor = this.currentDecorationIsVertical + ? new Vector2(b.Right, center.Y) // vertical: anchor right edge + : new Vector2(center.X, b.Top); // horizontal: anchor top edge + } + + // Scale about the chosen anchor so the fixed edge stays in place. + outline = outline.Transform(Matrix4x4.CreateScale(scale.X, scale.Y, 1, new Vector3(anchor, 0))); + } + } + + // Render the path here. Decorations are un-cached. + Point renderLocation = ClampToPixel(outline.Bounds.Location); + IPath decorationPath = outline.Translate(-renderLocation); + Brush decorationBrush = pen.StrokeFill; + this.DrawingOperations.Add(new DrawingOperation + { + Kind = DrawingOperationKind.Fill, + Path = decorationPath, + RenderLocation = renderLocation, + IntersectionRule = IntersectionRule.NonZero, + Brush = decorationBrush, + RenderPass = RenderOrderDecoration + }); + } + + /// + protected override void EndGlyph() + { + // If hasLayer is set, layers were already handled by EndLayer — skip. + // Otherwise, on a cache miss the built path is translated to local coordinates, + // stored for future hits, and emitted as fill and/or outline DrawingOperations. + // On a cache hit the stored path is reused with sub-pixel delta compensation. + if (this.hasLayer) + { + // The layer has already been rendered. + this.hasLayer = false; + return; + } + + GlyphRenderData renderData = default; + IPath? glyphPath = null; + + // Fix up the text runs colors. + // Only if both brush and pen is null do we fallback to the default value. + if (this.currentBrush == null && this.currentPen == null) + { + this.currentBrush = this.defaultBrush; + this.currentPen = this.defaultPen; + } + + bool renderFill = false; + bool renderOutline = false; + + // If we are using the fonts color layers we ignore the request to draw an outline only + // because that won't really work. Instead we force drawing using fill with the requested color. + if (this.currentBrush != null) + { + renderFill = true; + } + + if (this.currentPen != null) + { + renderOutline = true; + } + + // Path has already been added to the collection via the base class. + IPath path = this.CurrentPaths[^1]; + Point renderLocation = ClampToPixel(path.Bounds.Location); + if (this.noCache || this.rasterizationRequired) + { + if (path.Bounds.Equals(RectangleF.Empty)) + { + return; + } + + IPath localPath = path.Translate(-renderLocation); + if (renderFill || renderOutline) + { + renderData.FillPath = localPath; + glyphPath = renderData.FillPath; + } + + // Capture the delta between the location and the truncated render location. + // We can use this to offset the render location on the next instance of this glyph. + renderData.LocationDelta = (Vector2)(path.Bounds.Location - renderLocation); + + // Store the offset between outline bounds and font metric bounds so that + // cache hits in BeginGlyph can accurately estimate the path location. + renderData.BoundsOffset = (Vector2)(path.Bounds.Location - this.currentTransformedBoundsLocation); + + if (!this.noCache) + { + this.UpdateCache(renderData); + } + } + else + { + renderData = this.glyphCache[this.currentCacheKey][this.cacheReadIndex++]; + + // Offset the render location by the delta from the cached glyph and this one. + Vector2 previousDelta = renderData.LocationDelta; + Vector2 currentLocation = path.Bounds.Location; + Vector2 currentDelta = path.Bounds.Location - ClampToPixel(path.Bounds.Location); + + if (previousDelta.Y > currentDelta.Y) + { + // Move the location down to match the previous location offset. + currentLocation += new Vector2(0, previousDelta.Y - currentDelta.Y); + } + else if (previousDelta.Y < currentDelta.Y) + { + // Move the location up to match the previous location offset. + currentLocation -= new Vector2(0, currentDelta.Y - previousDelta.Y); + } + else if (previousDelta.X > currentDelta.X) + { + // Move the location right to match the previous location offset. + currentLocation += new Vector2(previousDelta.X - currentDelta.X, 0); + } + else if (previousDelta.X < currentDelta.X) + { + // Move the location left to match the previous location offset. + currentLocation -= new Vector2(currentDelta.X - previousDelta.X, 0); + } + + renderLocation = ClampToPixel(currentLocation); + + if (renderFill && renderData.FillPath is not null) + { + glyphPath = renderData.FillPath; + } + + if (renderOutline && renderData.FillPath is not null) + { + glyphPath = renderData.FillPath; + } + } + + if (renderFill && glyphPath is not null) + { + IntersectionRule fillRule = TextUtilities.MapFillRule(this.currentFillRule); + this.DrawingOperations.Add(new DrawingOperation + { + Kind = DrawingOperationKind.Fill, + Path = glyphPath, + RenderLocation = renderLocation, + IntersectionRule = fillRule, + Brush = this.currentBrush, + RenderPass = RenderOrderFill, + PixelAlphaCompositionMode = this.currentCompositionMode, + PixelColorBlendingMode = this.currentBlendingMode + }); + } + + if (renderOutline && glyphPath is not null) + { + IntersectionRule outlineRule = TextUtilities.MapFillRule(this.currentFillRule); + this.DrawingOperations.Add(new DrawingOperation + { + Kind = DrawingOperationKind.Draw, + Path = glyphPath, + RenderLocation = renderLocation, + IntersectionRule = outlineRule, + Pen = this.currentPen, + RenderPass = RenderOrderOutline, + PixelAlphaCompositionMode = this.currentCompositionMode, + PixelColorBlendingMode = this.currentBlendingMode + }); + } + } + + /// + /// Emits fill and/or outline s from a cached + /// entry. Called from on a + /// non-layered, decoration-free cache hit when the font engine is told to skip + /// the outline entirely (returns ). + /// + /// The cached render data containing the translated path and location delta. + /// The transformed bounding-box origin for the current glyph instance. + private void EmitCachedGlyphOperations(GlyphRenderData renderData, PointF currentBoundsLocation) + { + // Estimate the outline bounds location using the stored offset between + // the outline bounds and the font metric bounds from the original glyph. + PointF estimatedPathLocation = new( + currentBoundsLocation.X + renderData.BoundsOffset.X, + currentBoundsLocation.Y + renderData.BoundsOffset.Y); + Point renderLocation = ComputeCacheHitRenderLocation(estimatedPathLocation, renderData.LocationDelta); + + // Fix up the text runs colors. + Brush? brush = this.currentBrush; + Pen? pen = this.currentPen; + if (brush == null && pen == null) + { + brush = this.defaultBrush; + pen = this.defaultPen; + } + + IPath? glyphPath = renderData.FillPath; + if (glyphPath is null) + { + return; + } + + if (brush != null) + { + IntersectionRule fillRule = TextUtilities.MapFillRule(this.currentFillRule); + this.DrawingOperations.Add(new DrawingOperation + { + Kind = DrawingOperationKind.Fill, + Path = glyphPath, + RenderLocation = renderLocation, + IntersectionRule = fillRule, + Brush = brush, + RenderPass = RenderOrderFill, + PixelAlphaCompositionMode = this.currentCompositionMode, + PixelColorBlendingMode = this.currentBlendingMode + }); + } + + if (pen != null) + { + IntersectionRule outlineRule = TextUtilities.MapFillRule(this.currentFillRule); + this.DrawingOperations.Add(new DrawingOperation + { + Kind = DrawingOperationKind.Draw, + Path = glyphPath, + RenderLocation = renderLocation, + IntersectionRule = outlineRule, + Pen = pen, + RenderPass = RenderOrderOutline, + PixelAlphaCompositionMode = this.currentCompositionMode, + PixelColorBlendingMode = this.currentBlendingMode + }); + } + } + + /// + /// Computes the pixel-snapped render location for a cache-hit glyph by compensating + /// for the sub-pixel delta difference between the original cached glyph and the + /// current instance. This keeps glyphs visually aligned even when their sub-pixel + /// positions differ slightly. + /// + /// The estimated outline bounds origin for the current glyph. + /// The sub-pixel delta recorded when the path was first cached. + /// A pixel-snapped render location. + private static Point ComputeCacheHitRenderLocation(PointF pathLocation, Vector2 previousDelta) + { + Vector2 currentLocation = (Vector2)pathLocation; + Vector2 currentDelta = currentLocation - (Vector2)ClampToPixel(pathLocation); + + if (previousDelta.Y > currentDelta.Y) + { + currentLocation += new Vector2(0, previousDelta.Y - currentDelta.Y); + } + else if (previousDelta.Y < currentDelta.Y) + { + currentLocation -= new Vector2(0, currentDelta.Y - previousDelta.Y); + } + else if (previousDelta.X > currentDelta.X) + { + currentLocation += new Vector2(previousDelta.X - currentDelta.X, 0); + } + else if (previousDelta.X < currentDelta.X) + { + currentLocation -= new Vector2(currentDelta.X - previousDelta.X, 0); + } + + return ClampToPixel(currentLocation); + } + + /// + /// Stores a entry in the glyph cache under the + /// current key. Creates the cache list on first insertion for a given key. + /// + private void UpdateCache(GlyphRenderData renderData) + { + if (!this.glyphCache.TryGetValue(this.currentCacheKey, out List? _)) + { + this.glyphCache[this.currentCacheKey] = []; + } + + this.glyphCache[this.currentCacheKey].Add(renderData); + } + + /// + public void Dispose() => this.Dispose(true); + + /// + /// Truncates a floating-point position to the nearest whole pixel toward negative infinity. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Point ClampToPixel(PointF point) => Point.Truncate(point); + + /// + /// Applies the path-based transform to the + /// for the current glyph, positioning it along the text path (if any) or + /// leaving the identity transform for linear text. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void TransformGlyph(in FontRectangle bounds) + => this.Builder.SetTransform(this.ComputeTransform(in bounds)); + + /// + /// Computes the combined translation + rotation matrix that places a glyph + /// along the text path. For linear text (no path), returns . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Matrix4x4 ComputeTransform(in FontRectangle bounds) + { + if (this.path is null) + { + return Matrix4x4.Identity; + } + + // Find the point of this intersection along the given path. + // We want to find the point on the path that is closest to the center-bottom side of the glyph. + Vector2 half = new(bounds.Width * .5F, 0); + SegmentInfo pathPoint = this.path.PointAlongPath(bounds.Left + half.X); + + // Now offset to our target point since we're aligning the top-left location of our glyph against the path. + Vector2 translation = (Vector2)pathPoint.Point - bounds.Location - half + new Vector2(0, bounds.Top); + return Matrix4x4.CreateTranslation(translation.X, translation.Y, 0) * new Matrix4x4(Matrix3x2.CreateRotation(pathPoint.Angle - MathF.PI, (Vector2)pathPoint.Point)); + } + + /// + /// Releases managed resources (glyph cache and drawing operations list). + /// + /// to release managed resources. + private void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + this.glyphCache.Clear(); + this.DrawingOperations.Clear(); + } + + this.isDisposed = true; + } + } + + /// + /// Per-layer cached data for a rasterized glyph. Stores the locally-translated + /// path and the sub-pixel deltas needed to reposition the path at a different + /// screen location on a cache hit. + /// + private struct GlyphRenderData + { + /// + /// The fractional-pixel offset between the path's bounding-box origin + /// and the truncated (pixel-snapped) render location. Used to compensate + /// for sub-pixel position differences between cache hits. + /// + public Vector2 LocationDelta; + + /// + /// The offset between the outline path's bounding-box origin and the + /// font-metric bounds origin. Stored on first rasterization so that + /// can estimate the path location + /// from only the font-metric bounds (which are available without outline data). + /// + public Vector2 BoundsOffset; + + /// + /// The glyph outline path translated to local coordinates (origin at 0,0). + /// Shared across all cache hits for the same . + /// + public IPath? FillPath; + + /// + /// if this entry belongs to a multi-layer (COLR) glyph. + /// Non-layered cache hits with no decorations can skip the outline entirely + /// (return from ); layered hits + /// still need the per-layer BeginLayer/EndLayer callbacks. + /// + public bool IsLayered; + } + + /// + /// Identifies a unique glyph variant for caching purposes. Two glyphs with the same + /// share identical outline geometry and can reuse the same + /// . The key includes the glyph id, font metrics, + /// sub-pixel position (quantized to ), and the pen reference + /// (since stroke width affects the outline path). + /// + private readonly struct CacheKey : IEquatable + { + /// Gets the font family name. + public string Font { get; init; } + + /// Gets the glyph color variant (normal, COLR, etc.). + public GlyphColor GlyphColor { get; init; } + + /// Gets the glyph type (simple, composite, etc.). + public GlyphType GlyphType { get; init; } + + /// Gets the font style (regular, bold, italic, etc.). + public FontStyle FontStyle { get; init; } + + /// Gets the glyph index within the font. + public ushort GlyphId { get; init; } + + /// Gets the composite glyph parent index (0 for non-composite). + public ushort CompositeGlyphId { get; init; } + + /// Gets the Unicode code point this glyph represents. + public CodePoint CodePoint { get; init; } + + /// Gets the em-size at which the glyph is rendered. + public float PointSize { get; init; } + + /// Gets the DPI used for rendering. + public float Dpi { get; init; } + + /// Gets the layout mode (horizontal, vertical, vertical-rotated). + public GlyphLayoutMode LayoutMode { get; init; } + + /// Gets any text attributes (e.g. superscript/subscript) that affect rendering. + public TextAttributes TextAttributes { get; init; } + + /// Gets text decorations that may influence outline geometry. + public TextDecorations TextDecorations { get; init; } + + /// Gets the quantized sub-pixel bounds used for position-sensitive cache lookup. + public RectangleF Bounds { get; init; } + + /// + /// Gets the pen reference used for outlined text. Compared by reference equality + /// so that different pen instances (even with the same stroke width) produce + /// separate cache entries — this is correct because pen identity affects stroke + /// pattern and dash style. + /// + public Pen? PenReference { get; init; } + + public static bool operator ==(CacheKey left, CacheKey right) => left.Equals(right); + + public static bool operator !=(CacheKey left, CacheKey right) => !(left == right); + + /// + /// Creates a from glyph renderer parameters and quantized bounds. + /// The grapheme index is intentionally excluded because it varies per glyph instance + /// while the outline geometry remains the same for matching glyph+position. + /// + /// The glyph renderer parameters from the font engine. + /// Quantized sub-pixel bounds for position-sensitive lookup. + /// The pen reference for outlined text, or . + /// A new cache key. + public static CacheKey FromParameters( + in GlyphRendererParameters parameters, + RectangleF bounds, + Pen? penReference) + => new() + { + // Do not include the grapheme index as that will + // always vary per glyph instance. + Font = parameters.Font, + GlyphType = parameters.GlyphType, + FontStyle = parameters.FontStyle, + GlyphId = parameters.GlyphId, + CompositeGlyphId = parameters.CompositeGlyphId, + CodePoint = parameters.CodePoint, + PointSize = parameters.PointSize, + Dpi = parameters.Dpi, + LayoutMode = parameters.LayoutMode, + TextAttributes = parameters.TextRun.TextAttributes, + TextDecorations = parameters.TextRun.TextDecorations, + Bounds = bounds, + PenReference = penReference + }; + + public override bool Equals(object? obj) + => obj is CacheKey key && this.Equals(key); + + public bool Equals(CacheKey other) + => this.Font == other.Font && + this.GlyphColor.Equals(other.GlyphColor) && + this.GlyphType == other.GlyphType && + this.FontStyle == other.FontStyle && + this.GlyphId == other.GlyphId && + this.CompositeGlyphId == other.CompositeGlyphId && + this.CodePoint.Equals(other.CodePoint) && + this.PointSize == other.PointSize && + this.Dpi == other.Dpi && + this.LayoutMode == other.LayoutMode && + this.TextAttributes == other.TextAttributes && + this.TextDecorations == other.TextDecorations && + this.Bounds.Equals(other.Bounds) && + ReferenceEquals(this.PenReference, other.PenReference); + + public override int GetHashCode() + { + HashCode hash = default; + hash.Add(this.Font); + hash.Add(this.GlyphColor); + hash.Add(this.GlyphType); + hash.Add(this.FontStyle); + hash.Add(this.GlyphId); + hash.Add(this.CompositeGlyphId); + hash.Add(this.CodePoint); + hash.Add(this.PointSize); + hash.Add(this.Dpi); + hash.Add(this.LayoutMode); + hash.Add(this.TextAttributes); + hash.Add(this.TextDecorations); + hash.Add(this.Bounds); + hash.Add(this.PenReference is null ? 0 : RuntimeHelpers.GetHashCode(this.PenReference)); + return hash.ToHashCode(); + } + } +} diff --git a/src/ImageSharp.Drawing/Processing/RichTextOptions.cs b/src/ImageSharp.Drawing/Processing/RichTextOptions.cs index 268592a69..383a06772 100644 --- a/src/ImageSharp.Drawing/Processing/RichTextOptions.cs +++ b/src/ImageSharp.Drawing/Processing/RichTextOptions.cs @@ -25,7 +25,25 @@ public RichTextOptions(Font font) /// The options whose properties are copied into this instance. public RichTextOptions(RichTextOptions options) : base(options) - => this.Path = options.Path; + { + this.Path = options.Path; + List runs = new(options.TextRuns.Count); + foreach (RichTextRun run in options.TextRuns) + { + runs.Add(new RichTextRun() + { + Brush = run.Brush, + Pen = run.Pen, + StrikeoutPen = run.StrikeoutPen, + UnderlinePen = run.UnderlinePen, + OverlinePen = run.OverlinePen, + Start = run.Start, + End = run.End + }); + } + + this.TextRuns = runs; + } /// /// Gets or sets an optional collection of text runs to apply to the body of text. diff --git a/src/ImageSharp.Drawing/Processing/ShapeOptions.cs b/src/ImageSharp.Drawing/Processing/ShapeOptions.cs index bba986c04..79e0b6dc2 100644 --- a/src/ImageSharp.Drawing/Processing/ShapeOptions.cs +++ b/src/ImageSharp.Drawing/Processing/ShapeOptions.cs @@ -4,7 +4,8 @@ namespace SixLabors.ImageSharp.Drawing.Processing; /// -/// Options for influencing the drawing functions. +/// Provides options for controlling how vector shapes are interpreted during rasterization, +/// including the fill-rule intersection mode and boolean clipping operations. /// public class ShapeOptions : IDeepCloneable { @@ -22,14 +23,18 @@ private ShapeOptions(ShapeOptions source) } /// - /// Gets or sets the clipping operation. + /// Gets or sets the boolean clipping operation used when a clipping path is applied. + /// Determines how the clip shape interacts with the target region + /// (e.g. subtracts the clip shape). /// /// Defaults to . /// public BooleanOperation BooleanOperation { get; set; } = BooleanOperation.Difference; /// - /// Gets or sets the rule for calculating intersection points. + /// Gets or sets the fill rule that determines how overlapping or nested contours affect coverage. + /// fills any region with a non-zero winding number; + /// alternates fill/hole for each contour crossing. /// /// Defaults to . /// diff --git a/src/ImageSharp.Drawing/Processing/SolidBrush.cs b/src/ImageSharp.Drawing/Processing/SolidBrush.cs index 41c3e0717..7a23caad1 100644 --- a/src/ImageSharp.Drawing/Processing/SolidBrush.cs +++ b/src/ImageSharp.Drawing/Processing/SolidBrush.cs @@ -2,7 +2,8 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using SixLabors.ImageSharp.Drawing.Utilities; +using SixLabors.ImageSharp.Drawing.Helpers; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Drawing.Processing; @@ -26,8 +27,8 @@ public sealed class SolidBrush : Brush public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, - RectangleF region) => new SolidBrushApplicator(configuration, options, source, this.Color.ToPixel()); + Buffer2DRegion targetRegion, + RectangleF region) => new SolidBrushApplicator(configuration, options, targetRegion, this.Color.ToPixel()); /// public override bool Equals(Brush? other) @@ -59,35 +60,37 @@ private sealed class SolidBrushApplicator : BrushApplicator /// /// The configuration instance to use when performing operations. /// The graphics options. - /// The source image. + /// The destination pixel region. /// The color. public SolidBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, TPixel color) - : base(configuration, options, source) + : base(configuration, options, targetRegion) { - this.colors = configuration.MemoryAllocator.Allocate(source.Width); + this.colors = configuration.MemoryAllocator.Allocate(targetRegion.Width); this.colors.Memory.Span.Fill(color); // The threadlocal value is lazily invoked so there is no need to optionally create the type. - this.blenderBuffers = new ThreadLocalBlenderBuffers(configuration.MemoryAllocator, source.Width, true); + this.blenderBuffers = new ThreadLocalBlenderBuffers(configuration.MemoryAllocator, targetRegion.Width, true); } /// public override void Apply(Span scanline, int x, int y) { - Span destinationRow = this.Target.PixelBuffer.DangerousGetRowSpan(y).Slice(x); + int localY = y - this.TargetRegion.Rectangle.Y; + int localX = x - this.TargetRegion.Rectangle.X; + Span destinationRow = this.TargetRegion.DangerousGetRowSpan(localY)[localX..]; // Constrain the spans to each other if (destinationRow.Length > scanline.Length) { - destinationRow = destinationRow.Slice(0, scanline.Length); + destinationRow = destinationRow[..scanline.Length]; } else { - scanline = scanline.Slice(0, destinationRow.Length); + scanline = scanline[..destinationRow.Length]; } Configuration configuration = this.Configuration; @@ -98,7 +101,7 @@ public override void Apply(Span scanline, int x, int y) } else { - Span amounts = this.blenderBuffers.AmountSpan.Slice(0, scanline.Length); + Span amounts = this.blenderBuffers.AmountSpan[..scanline.Length]; for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Processing/StrokeOptions.cs b/src/ImageSharp.Drawing/Processing/StrokeOptions.cs index 51886f915..44e73969b 100644 --- a/src/ImageSharp.Drawing/Processing/StrokeOptions.cs +++ b/src/ImageSharp.Drawing/Processing/StrokeOptions.cs @@ -8,16 +8,6 @@ namespace SixLabors.ImageSharp.Drawing.Processing; /// public sealed class StrokeOptions : IEquatable { - /// - /// Gets or sets a value indicating whether stroked contours should be normalized by - /// resolving self-intersections and overlaps before returning. - /// - /// - /// Defaults to for maximum throughput. - /// When disabled, callers should rasterize with a non-zero winding fill rule. - /// - public bool NormalizeOutput { get; set; } - /// /// Gets or sets the miter limit used to clamp outer miter joins. /// @@ -50,28 +40,38 @@ public sealed class StrokeOptions : IEquatable /// public InnerJoin InnerJoin { get; set; } = InnerJoin.Miter; + /// + /// Gets or sets a value indicating whether stroked contours should be normalized + /// by resolving self-intersections and overlaps before returning. + /// + /// + /// Defaults to false for maximum throughput. When disabled, callers should rasterize + /// with a non-zero winding fill rule. + /// + public bool NormalizeOutput { get; set; } + /// public override bool Equals(object? obj) => this.Equals(obj as StrokeOptions); /// public bool Equals(StrokeOptions? other) => other is not null && - this.NormalizeOutput == other.NormalizeOutput && this.MiterLimit == other.MiterLimit && this.InnerMiterLimit == other.InnerMiterLimit && this.ArcDetailScale == other.ArcDetailScale && this.LineJoin == other.LineJoin && this.LineCap == other.LineCap && - this.InnerJoin == other.InnerJoin; + this.InnerJoin == other.InnerJoin && + this.NormalizeOutput == other.NormalizeOutput; /// public override int GetHashCode() => HashCode.Combine( - this.NormalizeOutput, this.MiterLimit, this.InnerMiterLimit, this.ArcDetailScale, this.LineJoin, this.LineCap, - this.InnerJoin); + this.InnerJoin, + this.NormalizeOutput); } diff --git a/src/ImageSharp.Drawing/Processing/SweepGradientBrush.cs b/src/ImageSharp.Drawing/Processing/SweepGradientBrush.cs index 5aed66787..b29b36c05 100644 --- a/src/ImageSharp.Drawing/Processing/SweepGradientBrush.cs +++ b/src/ImageSharp.Drawing/Processing/SweepGradientBrush.cs @@ -1,8 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Helpers; + namespace SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Memory; + /// /// Provides an implementation of a brush for painting sweep (conic) gradients within areas. /// Angles increase clockwise (y-down coordinate system) with 0° pointing to the +X direction. @@ -32,8 +37,40 @@ public SweepGradientBrush( : base(repetitionMode, colorStops) { this.center = center; - this.startAngleDegrees = startAngleDegrees; - this.endAngleDegrees = endAngleDegrees; + this.startAngleDegrees = NormalizeDegrees(startAngleDegrees); + this.endAngleDegrees = NormalizeDegrees(endAngleDegrees); + } + + /// + /// Gets the center point of the sweep gradient. + /// + public PointF Center => this.center; + + /// + /// Gets the starting angle in degrees. + /// + public float StartAngleDegrees => this.startAngleDegrees; + + /// + /// Gets the ending angle in degrees. + /// + public float EndAngleDegrees => this.endAngleDegrees; + + /// + public override Brush Transform(Matrix4x4 matrix) + { + PointF tc = PointF.Transform(this.center, matrix); + + float startRad = GeometryUtilities.DegreeToRadian(this.startAngleDegrees); + float endRad = GeometryUtilities.DegreeToRadian(this.endAngleDegrees); + + PointF startDir = PointF.Transform(new PointF(this.center.X + MathF.Cos(startRad), this.center.Y + MathF.Sin(startRad)), matrix); + PointF endDir = PointF.Transform(new PointF(this.center.X + MathF.Cos(endRad), this.center.Y + MathF.Sin(endRad)), matrix); + + float newStart = MathF.Atan2(startDir.Y - tc.Y, startDir.X - tc.X) * (180f / MathF.PI); + float newEnd = MathF.Atan2(endDir.Y - tc.Y, endDir.X - tc.X) * (180f / MathF.PI); + + return new SweepGradientBrush(tc, newStart, newEnd, this.RepetitionMode, this.ColorStopsArray); } /// @@ -58,20 +95,29 @@ public override int GetHashCode() this.startAngleDegrees, this.endAngleDegrees); + private static float NormalizeDegrees(float deg) + { + float d = deg % 360f; + if (d < 0f) + { + d += 360f; + } + + return d; + } + /// public override BrushApplicator CreateApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, + Buffer2DRegion targetRegion, RectangleF region) => new SweepGradientBrushApplicator( configuration, options, - source, - this.center, - this.startAngleDegrees, - this.endAngleDegrees, - this.ColorStops, + targetRegion, + this, + this.ColorStopsArray, this.RepetitionMode); /// @@ -98,42 +144,48 @@ private sealed class SweepGradientBrushApplicator : GradientBrushApplica /// /// The configuration instance to use when performing operations. /// The graphics options. - /// The source image. - /// The center of the sweep gradient. - /// The start angle in degrees (clockwise). - /// The end angle in degrees (clockwise). + /// The destination pixel region. + /// The sweep gradient brush. /// The gradient color stops (ratios in [0..1]). /// Defines how gradient colors are repeated outside [0..1]. public SweepGradientBrushApplicator( Configuration configuration, GraphicsOptions options, - ImageFrame source, - PointF center, - float startAngleDegrees, - float endAngleDegrees, + Buffer2DRegion targetRegion, + SweepGradientBrush brush, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) - : base(configuration, options, source, colorStops, repetitionMode) + : base(configuration, options, targetRegion, colorStops, repetitionMode) { - this.cx = center.X; - this.cy = center.Y; + this.cx = brush.Center.X; + this.cy = brush.Center.Y; - float start = GeometryUtilities.DegreeToRadian(NormalizeDegrees(startAngleDegrees)); - float end = GeometryUtilities.DegreeToRadian(NormalizeDegrees(endAngleDegrees)); + float startRad = GeometryUtilities.DegreeToRadian(brush.StartAngleDegrees); + float endRad = GeometryUtilities.DegreeToRadian(brush.EndAngleDegrees); - float sweep = NormalizeDeltaRadians(end - start); + float sweep = NormalizeDeltaRadians(endRad - startRad); - // If sweep collapses numerically to ~0, treat as full circle. if (MathF.Abs(sweep) < 1e-6f) { sweep = Tau; } - this.startRad = start; + this.startRad = startRad; this.invSweep = 1f / sweep; this.isFullCircle = MathF.Abs(sweep - Tau) < 1e-6f; } + private static float NormalizeDeltaRadians(float delta) + { + float d = delta % Tau; + if (d <= 0f) + { + d += Tau; + } + + return d; + } + /// /// Calculates the position parameter along the sweep gradient for the given device-space point. /// The returned value is not clamped to [0..1]; repetition semantics are applied by the base class. @@ -183,27 +235,5 @@ protected override float PositionOnGradient(float x, float y) // Partial sweep: phase beyond sweep -> t > 1 (lets repetition mode handle clipping). return phase * this.invSweep; } - - private static float NormalizeDegrees(float deg) - { - float d = deg % 360f; - if (d < 0f) - { - d += 360f; - } - - return d; - } - - private static float NormalizeDeltaRadians(float delta) - { - float d = delta % Tau; - if (d <= 0f) - { - d += Tau; - } - - return d; - } } } diff --git a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp.Drawing/RectangularPolygon.cs similarity index 99% rename from src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs rename to src/ImageSharp.Drawing/RectangularPolygon.cs index aec7e31e5..f23514602 100644 --- a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs +++ b/src/ImageSharp.Drawing/RectangularPolygon.cs @@ -156,7 +156,7 @@ public static explicit operator RectangularPolygon(Polygon polygon) => new(polygon.Bounds.X, polygon.Bounds.Y, polygon.Bounds.Width, polygon.Bounds.Height); /// - public IPath Transform(Matrix3x2 matrix) + public IPath Transform(Matrix4x4 matrix) { if (matrix.IsIdentity) { diff --git a/src/ImageSharp.Drawing/Shapes/RegularPolygon.cs b/src/ImageSharp.Drawing/RegularPolygon.cs similarity index 99% rename from src/ImageSharp.Drawing/Shapes/RegularPolygon.cs rename to src/ImageSharp.Drawing/RegularPolygon.cs index b914d5389..44d92d3db 100644 --- a/src/ImageSharp.Drawing/Shapes/RegularPolygon.cs +++ b/src/ImageSharp.Drawing/RegularPolygon.cs @@ -70,7 +70,7 @@ private static LinearLineSegment CreateSegment(PointF location, float radius, in PointF[] points = new PointF[vertices]; for (int i = 0; i < vertices; i++) { - PointF rotated = PointF.Transform(distanceVector, Matrix3x2.CreateRotation(current)); + PointF rotated = PointF.Transform(distanceVector, Matrix4x4.CreateRotationZ(current)); points[i] = rotated + location; diff --git a/src/ImageSharp.Drawing/Shapes/SegmentInfo.cs b/src/ImageSharp.Drawing/SegmentInfo.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/SegmentInfo.cs rename to src/ImageSharp.Drawing/SegmentInfo.cs diff --git a/src/ImageSharp.Drawing/Shapes/ClipPathExtensions.cs b/src/ImageSharp.Drawing/Shapes/ClipPathExtensions.cs deleted file mode 100644 index b9b3ccde9..000000000 --- a/src/ImageSharp.Drawing/Shapes/ClipPathExtensions.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Shapes.PolygonGeometry; - -namespace SixLabors.ImageSharp.Drawing; - -/// -/// Provides extension methods to that allow the clipping of shapes. -/// -public static class ClipPathExtensions -{ - private static readonly ShapeOptions DefaultOptions = new(); - - /// - /// Clips the specified subject path with the provided clipping paths. - /// - /// The subject path. - /// The clipping paths. - /// The clipped . - public static IPath Clip(this IPath subjectPath, params IPath[] clipPaths) - => subjectPath.Clip(DefaultOptions, clipPaths); - - /// - /// Clips the specified subject path with the provided clipping paths. - /// - /// The subject path. - /// The shape options. - /// The clipping paths. - /// The clipped . - public static IPath Clip( - this IPath subjectPath, - ShapeOptions options, - params IPath[] clipPaths) - => ClippedShapeGenerator.GenerateClippedShapes(options.BooleanOperation, subjectPath, clipPaths); - - /// - /// Clips the specified subject path with the provided clipping paths. - /// - /// The subject path. - /// The clipping paths. - /// The clipped . - public static IPath Clip(this IPath subjectPath, IEnumerable clipPaths) - => subjectPath.Clip(DefaultOptions, clipPaths); - - /// - /// Clips the specified subject path with the provided clipping paths. - /// - /// The subject path. - /// The shape options. - /// The clipping paths. - /// The clipped . - public static IPath Clip( - this IPath subjectPath, - ShapeOptions options, - IEnumerable clipPaths) - => ClippedShapeGenerator.GenerateClippedShapes(options.BooleanOperation, subjectPath, clipPaths); -} diff --git a/src/ImageSharp.Drawing/Shapes/Helpers/ArrayBuilder{T}.cs b/src/ImageSharp.Drawing/Shapes/Helpers/ArrayBuilder{T}.cs deleted file mode 100644 index c8e7cc26e..000000000 --- a/src/ImageSharp.Drawing/Shapes/Helpers/ArrayBuilder{T}.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Drawing.Shapes.Helpers; - -/// -/// A helper type for avoiding allocations while building arrays. -/// -/// The type of item contained in the array. -internal struct ArrayBuilder - where T : struct -{ - private const int DefaultCapacity = 4; - - // Starts out null, initialized on first Add. - private T[]? data; - private int size; - - /// - /// Initializes a new instance of the struct. - /// - /// The initial capacity of the array. - public ArrayBuilder(int capacity) - : this() - { - if (capacity > 0) - { - this.data = new T[capacity]; - } - } - - /// - /// Gets or sets the number of items in the array. - /// - public int Length - { - readonly get => this.size; - - set - { - if (value > 0) - { - this.EnsureCapacity(value); - this.size = value; - } - else - { - this.size = 0; - } - } - } - - /// - /// Returns a reference to specified element of the array. - /// - /// The index of the element to return. - /// The . - /// - /// Thrown when index less than 0 or index greater than or equal to . - /// - public readonly ref T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - DebugGuard.MustBeBetweenOrEqualTo(index, 0, this.size, nameof(index)); - return ref this.data![index]; - } - } - - /// - /// Adds the given item to the array. - /// - /// The item to add. - public void Add(T item) - { - int position = this.size; - T[]? array = this.data; - - if (array != null && (uint)position < (uint)array.Length) - { - this.size = position + 1; - array[position] = item; - } - else - { - this.AddWithResize(item); - } - } - - // Non-inline from Add to improve its code quality as uncommon path - [MethodImpl(MethodImplOptions.NoInlining)] - private void AddWithResize(T item) - { - int size = this.size; - this.Grow(size + 1); - this.size = size + 1; - this.data[size] = item; - } - - /// - /// Remove the last item from the array. - /// - public void RemoveLast() - { - DebugGuard.MustBeGreaterThan(this.size, 0, nameof(this.size)); - this.size--; - } - - /// - /// Clears the array. - /// Allocated memory is left intact for future usage. - /// - public void Clear() => - - // No need to actually clear since we're not allowing reference types. - this.size = 0; - - private void EnsureCapacity(int min) - { - int length = this.data?.Length ?? 0; - if (length < min) - { - this.Grow(min); - } - } - - [MemberNotNull(nameof(this.data))] - private void Grow(int capacity) - { - // Same expansion algorithm as List. - int length = this.data?.Length ?? 0; - int newCapacity = length == 0 ? DefaultCapacity : length * 2; - if ((uint)newCapacity > Array.MaxLength) - { - newCapacity = Array.MaxLength; - } - - if (newCapacity < capacity) - { - newCapacity = capacity; - } - - T[] array = new T[newCapacity]; - - if (this.size > 0) - { - Array.Copy(this.data!, array, this.size); - } - - this.data = array; - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Helpers/ArrayExtensions.cs b/src/ImageSharp.Drawing/Shapes/Helpers/ArrayExtensions.cs deleted file mode 100644 index 1e2e9c103..000000000 --- a/src/ImageSharp.Drawing/Shapes/Helpers/ArrayExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing; - -/// -/// Extensions on arrays. -/// -internal static class ArrayExtensions -{ - /// - /// Merges the specified source2. - /// - /// the type of the array - /// The source1. - /// The source2. - /// the Merged arrays - public static T[] Merge(this T[] source1, T[] source2) - { - if (source2 is null || source2.Length == 0) - { - return source1; - } - - T[] target = new T[source1.Length + source2.Length]; - - for (int i = 0; i < source1.Length; i++) - { - target[i] = source1[i]; - } - - for (int i = 0; i < source2.Length; i++) - { - target[i + source1.Length] = source2[i]; - } - - return target; - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Helpers/TopologyUtilities.cs b/src/ImageSharp.Drawing/Shapes/Helpers/TopologyUtilities.cs deleted file mode 100644 index 6f5c2f401..000000000 --- a/src/ImageSharp.Drawing/Shapes/Helpers/TopologyUtilities.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Shapes.Helpers; - -/// -/// Implements some basic algorithms on raw data structures. -/// Polygons are represented with a span of points, -/// where first point should be repeated at the end. -/// -/// -/// Positive orientation means Clockwise in world coordinates (positive direction goes UP on paper). -/// Since the Drawing library deals mostly with Screen coordinates where this is opposite, -/// we use different terminology here to avoid confusion. -/// -internal static class TopologyUtilities -{ - /// - /// Positive: CCW in world coords (CW on screen) - /// Negative: CW in world coords (CCW on screen) - /// - public static void EnsureOrientation(Span polygon, int expectedOrientation) - { - if (GetPolygonOrientation(polygon) * expectedOrientation < 0) - { - polygon.Reverse(); - } - } - - /// - /// Zero: area is 0 - /// Positive: CCW in world coords (CW on screen) - /// Negative: CW in world coords (CCW on screen) - /// - private static int GetPolygonOrientation(ReadOnlySpan polygon) - { - float sum = 0f; - for (int i = 0; i < polygon.Length - 1; ++i) - { - PointF curr = polygon[i]; - PointF next = polygon[i + 1]; - sum += (curr.X * next.Y) - (next.X * curr.Y); - } - - // Normally, this should be a tolerant comparison, we don't have a special path for zero-area - // (or for self-intersecting, semi-zero-area) polygons in edge scanning. - return Math.Sign(sum); - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Helpers/VectorExtensions.cs b/src/ImageSharp.Drawing/Shapes/Helpers/VectorExtensions.cs deleted file mode 100644 index 849c93e79..000000000 --- a/src/ImageSharp.Drawing/Shapes/Helpers/VectorExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Drawing; - -/// -/// Extensions on arrays. -/// -internal static class VectorExtensions -{ - /// - /// Merges the specified source2. - /// - /// The source1. - /// The source2. - /// The threshold. - /// - /// the Merged arrays - /// - public static bool Equivalent(this PointF source1, PointF source2, float threshold) - { - Vector2 abs = Vector2.Abs(source1 - source2); - - return abs.X < threshold && abs.Y < threshold; - } - - /// - /// Merges the specified source2. - /// - /// The source1. - /// The source2. - /// The threshold. - /// - /// the Merged arrays - /// - public static bool Equivalent(this Vector2 source1, Vector2 source2, float threshold) - { - Vector2 abs = Vector2.Abs(source1 - source2); - - return abs.X < threshold && abs.Y < threshold; - } -} diff --git a/src/ImageSharp.Drawing/Shapes/InternalPath.cs b/src/ImageSharp.Drawing/Shapes/InternalPath.cs deleted file mode 100644 index cc6a53ea0..000000000 --- a/src/ImageSharp.Drawing/Shapes/InternalPath.cs +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing; - -/// -/// Internal logic for integrating linear paths. -/// -internal class InternalPath -{ - /// - /// The epsilon for float comparison - /// - private const float Epsilon = 0.003f; - private const float Epsilon2 = 0.2f; - - /// - /// The maximum vector - /// - private static readonly Vector2 MaxVector = new(float.MaxValue); - - /// - /// The points. - /// - private readonly PointData[] points; - - /// - /// The closed path. - /// - private readonly bool closedPath; - - /// - /// Initializes a new instance of the class. - /// - /// The segments. - /// if set to true [is closed path]. - /// Whether to remove close and collinear vertices - internal InternalPath(IReadOnlyList segments, bool isClosedPath, bool removeCloseAndCollinear = true) - : this(Simplify(segments, isClosedPath, removeCloseAndCollinear), isClosedPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The segment. - /// if set to true [is closed path]. - internal InternalPath(ILineSegment segment, bool isClosedPath) - : this(segment?.Flatten() ?? Array.Empty(), isClosedPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The points. - /// if set to true [is closed path]. - internal InternalPath(ReadOnlyMemory points, bool isClosedPath) - : this(Simplify(points.Span, isClosedPath, true), isClosedPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The points. - /// if set to true [is closed path]. - private InternalPath(PointData[] points, bool isClosedPath) - { - this.points = points; - this.closedPath = isClosedPath; - - if (this.points.Length > 0) - { - float minX, minY, maxX, maxY, length; - length = 0; - minX = minY = float.MaxValue; - maxX = maxY = float.MinValue; - - foreach (PointData point in this.points) - { - length += point.Length; - minX = Math.Min(point.Point.X, minX); - minY = Math.Min(point.Point.Y, minY); - maxX = Math.Max(point.Point.X, maxX); - maxY = Math.Max(point.Point.Y, maxY); - } - - this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY); - this.Length = length; - } - else - { - this.Bounds = RectangleF.Empty; - this.Length = 0; - } - } - - /// - /// Gets the bounds. - /// - /// - /// The bounds. - /// - public RectangleF Bounds { get; } - - /// - /// Gets the length. - /// - /// - /// The length. - /// - public float Length { get; } - - /// - /// Gets the length. - /// - public int PointCount => this.points.Length; - - /// - /// Gets the points. - /// - /// The - internal ReadOnlyMemory Points() => this.points.Select(x => x.Point).ToArray(); - - /// - /// Calculates the point a certain distance a path. - /// - /// The distance along the path to find details of. - /// - /// Returns details about a point along a path. - /// - /// Thrown if no points found. - internal SegmentInfo PointAlongPath(float distanceAlongPath) - { - int pointCount = this.PointCount; - if (this.closedPath) - { - // Move the distance back to the beginning since this is a closed polygon. - distanceAlongPath %= this.Length; - pointCount--; - } - - for (int i = 0; i < pointCount; i++) - { - int next = WrapArrayIndex(i + 1, this.PointCount); - if (distanceAlongPath < this.points[next].Length) - { - float t = distanceAlongPath / this.points[next].Length; - Vector2 point = Vector2.Lerp(this.points[i].Point, this.points[next].Point, t); - Vector2 diff = this.points[i].Point - this.points[next].Point; - - return new SegmentInfo - { - Point = point, - Angle = (float)(Math.Atan2(diff.Y, diff.X) % (Math.PI * 2)) - }; - } - - distanceAlongPath -= this.points[next].Length; - } - - // Closed paths will never reach this point. - // For open paths we're going to create a new virtual point that extends past the path. - // The position and angle for that point are calculated based upon the last two points. - PointF a = this.points[Math.Max(this.points.Length - 2, 0)].Point; - PointF b = this.points[this.points.Length - 1].Point; - Vector2 delta = a - b; - float angle = (float)(Math.Atan2(delta.Y, delta.X) % (Math.PI * 2)); - - Matrix3x2 transform = Matrix3x2.CreateRotation(angle - MathF.PI) * Matrix3x2.CreateTranslation(b.X, b.Y); - - return new SegmentInfo - { - Point = Vector2.Transform(new Vector2(distanceAlongPath, 0), transform), - Angle = angle - }; - } - - internal IMemoryOwner ExtractVertices(MemoryAllocator allocator) - { - IMemoryOwner buffer = allocator.Allocate(this.points.Length + 1); - Span span = buffer.Memory.Span; - - for (int i = 0; i < this.points.Length; i++) - { - span[i] = this.points[i].Point; - } - - return buffer; - } - - // Modulo is a very slow operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int WrapArrayIndex(int i, int arrayLength) => i < arrayLength ? i : i - arrayLength; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static PointOrientation CalculateOrientation(Vector2 p, Vector2 q, Vector2 r) - { - // See http://www.geeksforgeeks.org/orientation-3-ordered-points/ - // for details of below formula. - Vector2 qp = q - p; - Vector2 rq = r - q; - float val = (qp.Y * rq.X) - (qp.X * rq.Y); - - if (val is > -Epsilon and < Epsilon) - { - return PointOrientation.Collinear; // colinear - } - - return (val > 0) ? PointOrientation.Clockwise : PointOrientation.Counterclockwise; // clock or counterclock wise - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static PointOrientation CalculateOrientation(Vector2 qp, Vector2 rq) - { - // See http://www.geeksforgeeks.org/orientation-3-ordered-points/ - // for details of below formula. - float val = (qp.Y * rq.X) - (qp.X * rq.Y); - - if (val > -Epsilon && val < Epsilon) - { - return PointOrientation.Collinear; // colinear - } - - return (val > 0) ? PointOrientation.Clockwise : PointOrientation.Counterclockwise; // clock or counterclock wise - } - - /// - /// Simplifies the collection of segments. - /// - /// The segments. - /// Weather the path is closed or open. - /// Whether to remove close and collinear vertices - /// - /// The . - /// - private static PointData[] Simplify(IReadOnlyList segments, bool isClosed, bool removeCloseAndCollinear) - { - List simplified = new(segments.Count); - - foreach (ILineSegment seg in segments) - { - ReadOnlyMemory points = seg.Flatten(); - simplified.AddRange(points.Span); - } - - return Simplify(CollectionsMarshal.AsSpan(simplified), isClosed, removeCloseAndCollinear); - } - - private static PointData[] Simplify(ReadOnlySpan points, bool isClosed, bool removeCloseAndCollinear) - { - int polyCorners = points.Length; - if (polyCorners == 0) - { - return []; - } - - List results = new(polyCorners); - Vector2 lastPoint = points[0]; - - if (!isClosed) - { - results.Add(new PointData - { - Point = points[0], - Orientation = PointOrientation.Collinear, - Length = 0 - }); - } - else - { - int prev = polyCorners; - do - { - prev--; - if (prev == 0) - { - // All points are common, shouldn't match anything - results.Add( - new PointData - { - Point = points[0], - Orientation = PointOrientation.Collinear, - Length = 0, - }); - - return [.. results]; - } - } - while (removeCloseAndCollinear && points[0].Equivalent(points[prev], Epsilon2)); // skip points too close together - - polyCorners = prev + 1; - lastPoint = points[prev]; - - results.Add( - new PointData - { - Point = points[0], - Orientation = CalculateOrientation(lastPoint, points[0], points[1]), - Length = Vector2.Distance(lastPoint, points[0]), - }); - - lastPoint = points[0]; - } - - for (int i = 1; i < polyCorners; i++) - { - int next = WrapArrayIndex(i + 1, polyCorners); - PointOrientation or = CalculateOrientation(lastPoint, points[i], points[next]); - if (or == PointOrientation.Collinear && next != 0) - { - continue; - } - - results.Add( - new PointData - { - Point = points[i], - Orientation = or, - Length = Vector2.Distance(lastPoint, points[i]), - }); - lastPoint = points[i]; - } - - if (isClosed && removeCloseAndCollinear) - { - // walk back removing collinear points - while (results.Count > 2 && results[^1].Orientation == PointOrientation.Collinear) - { - results.RemoveAt(results.Count - 1); - } - } - - return [.. results]; - } - - private struct PointData - { - public PointF Point; - public PointOrientation Orientation; - public float Length; - } -} diff --git a/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs b/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs deleted file mode 100644 index 78ff4fc15..000000000 --- a/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Shapes.PolygonGeometry; - -namespace SixLabors.ImageSharp.Drawing; - -/// -/// Extensions to that allow the generation of outlines. -/// -public static class OutlinePathExtensions -{ - private static readonly StrokeOptions DefaultOptions = new(); - - /// - /// Generates an outline of the path. - /// - /// The path to outline - /// The outline width. - /// A new representing the outline. - public static IPath GenerateOutline(this IPath path, float width) - => GenerateOutline(path, width, DefaultOptions); - - /// - /// Generates an outline of the path. - /// - /// The path to outline - /// The outline width. - /// The stroke geometry options. - /// A new representing the outline. - public static IPath GenerateOutline(this IPath path, float width, StrokeOptions strokeOptions) - { - if (width <= 0) - { - return Path.Empty; - } - - return StrokedShapeGenerator.GenerateStrokedShapes(path, width, strokeOptions); - } - - /// - /// Generates an outline of the path with alternating on and off segments based on the pattern. - /// - /// The path to outline - /// The outline width. - /// The pattern made of multiples of the width. - /// A new representing the outline. - public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan pattern) - => path.GenerateOutline(width, pattern, false); - - /// - /// Generates an outline of the path with alternating on and off segments based on the pattern. - /// - /// The path to outline - /// The outline width. - /// The pattern made of multiples of the width. - /// The stroke geometry options. - /// A new representing the outline. - public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan pattern, StrokeOptions strokeOptions) - => GenerateOutline(path, width, pattern, false, strokeOptions); - - /// - /// Generates an outline of the path with alternating on and off segments based on the pattern. - /// - /// The path to outline - /// The outline width. - /// The pattern made of multiples of the width. - /// Whether the first item in the pattern is on or off. - /// A new representing the outline. - public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan pattern, bool startOff) - => GenerateOutline(path, width, pattern, startOff, DefaultOptions); - - /// - /// Generates an outline of the path with alternating on and off segments based on the pattern. - /// - /// The path to outline - /// The outline width. - /// The pattern made of multiples of the width. - /// Whether the first item in the pattern is on or off. - /// The stroke geometry options. - /// A new representing the outline. - public static IPath GenerateOutline( - this IPath path, - float width, - ReadOnlySpan pattern, - bool startOff, - StrokeOptions strokeOptions) - { - if (width <= 0) - { - return Path.Empty; - } - - if (pattern.Length < 2) - { - return path.GenerateOutline(width, strokeOptions); - } - - const float eps = 1e-6f; - const int maxPatternSegments = 10000; - - // Compute the absolute pattern length in path units to detect degenerate patterns. - float patternLength = 0f; - for (int i = 0; i < pattern.Length; i++) - { - patternLength += MathF.Abs(pattern[i]) * width; - } - - // Fallback to a solid outline when the dash pattern is too small to be meaningful. - if (patternLength <= eps) - { - return path.GenerateOutline(width, strokeOptions); - } - - IEnumerable paths = path.Flatten(); - - List outlines = []; - List buffer = new(64); // arbitrary initial capacity hint. - - foreach (ISimplePath p in paths) - { - bool online = !startOff; - int patternPos = 0; - float targetLength = pattern[patternPos] * width; - - ReadOnlySpan pts = p.Points.Span; - if (pts.Length < 2) - { - continue; - } - - // number of edges to traverse (no wrap for open paths) - int edgeCount = p.IsClosed ? pts.Length : pts.Length - 1; - float totalLength = 0f; - - // Compute total path length to estimate the number of dash segments to produce. - for (int j = 0; j < edgeCount; j++) - { - int nextIndex = p.IsClosed ? (j + 1) % pts.Length : j + 1; - totalLength += Vector2.Distance(pts[j], pts[nextIndex]); - } - - if (totalLength > eps) - { - // Avoid runaway segmentation by falling back when the dash count explodes. - float estimatedSegments = (totalLength / patternLength) * pattern.Length; - if (estimatedSegments > maxPatternSegments) - { - return path.GenerateOutline(width, strokeOptions); - } - } - - int i = 0; - Vector2 current = pts[0]; - - while (i < edgeCount) - { - int nextIndex = p.IsClosed ? (i + 1) % pts.Length : i + 1; - Vector2 next = pts[nextIndex]; - float segLen = Vector2.Distance(current, next); - - // Skip degenerate segments. - if (segLen <= eps) - { - current = next; - i++; - continue; - } - - // Accumulate into the current dash span when the segment is shorter than the target. - if (segLen + eps < targetLength) - { - buffer.Add(current); - current = next; - i++; - targetLength -= segLen; - continue; - } - - // Close out a dash span when the segment length matches the target length. - if (MathF.Abs(segLen - targetLength) <= eps) - { - buffer.Add(current); - buffer.Add(next); - - if (online && buffer.Count >= 2 && buffer[0] != buffer[^1]) - { - outlines.Add([.. buffer]); - } - - buffer.Clear(); - online = !online; - - current = next; - i++; - patternPos = (patternPos + 1) % pattern.Length; - targetLength = pattern[patternPos] * width; - continue; - } - - // Split inside this segment to end the current dash span. - float t = targetLength / segLen; // 0 < t < 1 here - Vector2 split = current + (t * (next - current)); - - buffer.Add(current); - buffer.Add(split); - - if (online && buffer.Count >= 2 && buffer[0] != buffer[^1]) - { - outlines.Add([.. buffer]); - } - - buffer.Clear(); - online = !online; - - current = split; // continue along the same geometric segment - - patternPos = (patternPos + 1) % pattern.Length; - targetLength = pattern[patternPos] * width; - } - - // flush tail of the last dash span, if any - if (buffer.Count > 0) - { - buffer.Add(current); // terminate at the true end position - - if (online && buffer.Count >= 2 && buffer[0] != buffer[^1]) - { - outlines.Add([.. buffer]); - } - - buffer.Clear(); - } - } - - // Each outline span is stroked as an open polyline; the union cleans overlaps. - return StrokedShapeGenerator.GenerateStrokedShapes(outlines, width, strokeOptions); - } -} diff --git a/src/ImageSharp.Drawing/Shapes/PolygonGeometry/ClipperException.cs b/src/ImageSharp.Drawing/Shapes/PolygonGeometry/ClipperException.cs deleted file mode 100644 index d22aff793..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonGeometry/ClipperException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Drawing.Shapes.PolygonGeometry; - -/// -/// The exception that is thrown when an error occurs clipping a polygon. -/// -public class ClipperException : Exception -{ - /// - /// Initializes a new instance of the class. - /// - public ClipperException() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public ClipperException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the class with a specified error message and a - /// reference to the inner exception that is the cause of this exception. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a - /// reference if no inner exception is specified. - public ClipperException(string message, Exception innerException) - : base(message, innerException) - { - } -} diff --git a/src/ImageSharp.Drawing/Shapes/PolygonGeometry/StrokedShapeGenerator.cs b/src/ImageSharp.Drawing/Shapes/PolygonGeometry/StrokedShapeGenerator.cs deleted file mode 100644 index a3dc75836..000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonGeometry/StrokedShapeGenerator.cs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.PolygonClipper; - -using PCPolygon = SixLabors.PolygonClipper.Polygon; -using StrokeOptions = SixLabors.ImageSharp.Drawing.Processing.StrokeOptions; - -namespace SixLabors.ImageSharp.Drawing.Shapes.PolygonGeometry; - -/// -/// Generates stroked and merged shapes using polygon stroking and boolean clipping. -/// -internal static class StrokedShapeGenerator -{ - /// - /// Strokes a collection of dashed polyline spans and returns a merged outline. - /// - /// - /// The input spans. Each array is treated as an open polyline - /// and is stroked using the current stroker settings. - /// Spans that are null or contain fewer than 2 points are ignored. - /// - /// The stroke width in the caller's coordinate space. - /// The stroke geometry options. - /// - /// A representing the stroked outline after boolean merge. - /// - public static ComplexPolygon GenerateStrokedShapes(List spans, float width, StrokeOptions options) - { - // 1) Stroke each dashed span as open. - PCPolygon rings = new(spans.Count); - foreach (PointF[] span in spans) - { - if (span == null || span.Length < 2) - { - continue; - } - - Contour ring = new(span.Length); - for (int i = 0; i < span.Length; i++) - { - PointF p = span[i]; - ring.Add(new Vertex(p.X, p.Y)); - } - - rings.Add(ring); - } - - int count = rings.Count; - if (count == 0) - { - return new([]); - } - - PCPolygon result = PolygonStroker.Stroke(rings, width, CreateStrokeOptions(options)); - - IPath[] shapes = new IPath[result.Count]; - int index = 0; - for (int i = 0; i < result.Count; i++) - { - Contour contour = result[i]; - PointF[] points = new PointF[contour.Count]; - - for (int j = 0; j < contour.Count; j++) - { - Vertex vertex = contour[j]; - points[j] = new PointF((float)vertex.X, (float)vertex.Y); - } - - shapes[index++] = new Polygon(points); - } - - return new(shapes); - } - - /// - /// Strokes a path and returns a merged outline from its flattened segments. - /// - /// The source path. It is flattened using the current flattening settings. - /// The stroke width in the caller's coordinate space. - /// The stroke geometry options. - /// - /// A representing the stroked outline after boolean merge. - /// - public static ComplexPolygon GenerateStrokedShapes(IPath path, float width, StrokeOptions options) - { - // 1) Stroke the input path as open or closed. - PCPolygon rings = []; - - foreach (ISimplePath sp in path.Flatten()) - { - ReadOnlySpan span = sp.Points.Span; - - if (span.Length < 2) - { - continue; - } - - Contour ring = new(span.Length); - for (int i = 0; i < span.Length; i++) - { - PointF p = span[i]; - ring.Add(new Vertex(p.X, p.Y)); - } - - if (sp.IsClosed) - { - ring.Add(ring[0]); - } - - rings.Add(ring); - } - - int count = rings.Count; - if (count == 0) - { - return new([]); - } - - PCPolygon result = PolygonStroker.Stroke(rings, width, CreateStrokeOptions(options)); - - IPath[] shapes = new IPath[result.Count]; - int index = 0; - for (int i = 0; i < result.Count; i++) - { - Contour contour = result[i]; - PointF[] points = new PointF[contour.Count]; - - for (int j = 0; j < contour.Count; j++) - { - Vertex vertex = contour[j]; - points[j] = new PointF((float)vertex.X, (float)vertex.Y); - } - - shapes[index++] = new Polygon(points); - } - - return new(shapes); - } - - private static PolygonClipper.StrokeOptions CreateStrokeOptions(StrokeOptions options) - { - PolygonClipper.StrokeOptions o = new() - { - ArcDetailScale = options.ArcDetailScale, - MiterLimit = options.MiterLimit, - InnerMiterLimit = options.InnerMiterLimit, - NormalizeOutput = options.NormalizeOutput, - LineJoin = options.LineJoin switch - { - LineJoin.MiterRound => PolygonClipper.LineJoin.MiterRound, - LineJoin.Bevel => PolygonClipper.LineJoin.Bevel, - LineJoin.Round => PolygonClipper.LineJoin.Round, - LineJoin.MiterRevert => PolygonClipper.LineJoin.MiterRevert, - _ => PolygonClipper.LineJoin.Miter, - }, - - InnerJoin = options.InnerJoin switch - { - InnerJoin.Round => PolygonClipper.InnerJoin.Round, - InnerJoin.Miter => PolygonClipper.InnerJoin.Miter, - InnerJoin.Jag => PolygonClipper.InnerJoin.Jag, - _ => PolygonClipper.InnerJoin.Bevel, - }, - - LineCap = options.LineCap switch - { - LineCap.Round => PolygonClipper.LineCap.Round, - LineCap.Square => PolygonClipper.LineCap.Square, - _ => PolygonClipper.LineCap.Butt, - } - }; - - return o; - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/DefaultRasterizer.cs b/src/ImageSharp.Drawing/Shapes/Rasterization/DefaultRasterizer.cs deleted file mode 100644 index 2544ff5d9..000000000 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/DefaultRasterizer.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Shapes.Rasterization; - -/// -/// Default CPU rasterizer. -/// -/// -/// This rasterizer delegates to , which performs fixed-point -/// area/cover scanning and chooses an internal execution strategy (parallel row-tiles when -/// profitable, sequential fallback otherwise). -/// -internal sealed class DefaultRasterizer : IRasterizer -{ - /// - /// Gets the singleton default rasterizer instance. - /// - public static DefaultRasterizer Instance { get; } = new(); - - /// - public void Rasterize( - IPath path, - in RasterizerOptions options, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - { - Guard.NotNull(path, nameof(path)); - Guard.NotNull(allocator, nameof(allocator)); - Guard.NotNull(scanlineHandler, nameof(scanlineHandler)); - - Rectangle interest = options.Interest; - if (interest.Equals(Rectangle.Empty)) - { - return; - } - - PolygonScanner.Rasterize(path, options, allocator, ref state, scanlineHandler); - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/IRasterizer.cs b/src/ImageSharp.Drawing/Shapes/Rasterization/IRasterizer.cs deleted file mode 100644 index af642517a..000000000 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/IRasterizer.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Shapes.Rasterization; - -/// -/// Delegate invoked for each rasterized scanline. -/// -/// The caller-provided state type. -/// The destination y coordinate. -/// Coverage values for the scanline. -/// Caller-provided mutable state. -internal delegate void RasterizerScanlineHandler(int y, Span scanline, ref TState state) - where TState : struct; - -/// -/// Defines a rasterizer capable of converting vector paths into per-pixel scanline coverage. -/// -internal interface IRasterizer -{ - /// - /// Rasterizes a path into scanline coverage and invokes - /// for each non-empty destination row. - /// - /// The caller-provided state type. - /// The path to rasterize. - /// Rasterization options. - /// The memory allocator used for temporary buffers. - /// Caller-provided mutable state passed to the callback. - /// - /// Callback invoked for each rasterized scanline. Implementations should invoke this callback - /// in ascending y order and not concurrently for a single invocation. - /// - void Rasterize( - IPath path, - in RasterizerOptions options, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct; -} diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs deleted file mode 100644 index 150b2612c..000000000 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs +++ /dev/null @@ -1,2275 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Shapes.Rasterization; - -/// -/// Fixed-point polygon scanner that converts polygon edges into per-row coverage runs. -/// -/// -/// The scanner has two execution modes: -/// 1. Parallel tiled execution (default): build an edge table once, bucket edges by tile rows, -/// rasterize tiles in parallel with worker-local scratch, then emit in deterministic Y order. -/// 2. Sequential execution: reuse the same edge table and process band buckets on one thread. -/// -/// Both modes share the same coverage math and fill-rule handling, ensuring predictable output -/// regardless of scheduling strategy. -/// -internal static class PolygonScanner -{ - // Upper bound for temporary scanner buffers (bit vectors + cover/area + start-cover rows). - // Keeping this bounded prevents pathological full-image allocations on very large interests. - private const long BandMemoryBudgetBytes = 64L * 1024L * 1024L; - - // Blaze-style tile height used by the parallel row-tiling pipeline. - private const int DefaultTileHeight = 16; - - // Cap for buffered output coverage in the parallel path. We buffer one float per destination - // pixel plus one dirty-row byte per tile row before deterministic ordered emission. - private const long ParallelOutputPixelBudget = 16L * 1024L * 1024L; // 4096 x 4096 - - private const int FixedShift = 8; - private const int FixedOne = 1 << FixedShift; - private static readonly int WordBitCount = nint.Size * 8; - private const int AreaToCoverageShift = 9; - private const int CoverageStepCount = 256; - private const int EvenOddMask = (CoverageStepCount * 2) - 1; - private const int EvenOddPeriod = CoverageStepCount * 2; - private const float CoverageScale = 1F / CoverageStepCount; - - /// - /// Rasterizes the path using the default execution policy. - /// - /// The caller-owned mutable state type. - /// Path to rasterize. - /// Rasterization options. - /// Temporary buffer allocator. - /// Caller-owned mutable state. - /// Scanline callback invoked in ascending Y order. - public static void Rasterize( - IPath path, - in RasterizerOptions options, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - => RasterizeCore(path, options, allocator, ref state, scanlineHandler, allowParallel: true); - - /// - /// Rasterizes the path using the forced sequential policy. - /// - /// The caller-owned mutable state type. - /// Path to rasterize. - /// Rasterization options. - /// Temporary buffer allocator. - /// Caller-owned mutable state. - /// Scanline callback invoked in ascending Y order. - public static void RasterizeSequential( - IPath path, - in RasterizerOptions options, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - => RasterizeCore(path, options, allocator, ref state, scanlineHandler, allowParallel: false); - - /// - /// Shared entry point used by both public execution policies. - /// - /// The caller-owned mutable state type. - /// Path to rasterize. - /// Rasterization options. - /// Temporary buffer allocator. - /// Caller-owned mutable state. - /// Scanline callback invoked in ascending Y order. - /// - /// If , the scanner may use parallel tiled execution when profitable. - /// - private static void RasterizeCore( - IPath path, - in RasterizerOptions options, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler, - bool allowParallel) - where TState : struct - { - Rectangle interest = options.Interest; - int width = interest.Width; - int height = interest.Height; - if (width <= 0 || height <= 0) - { - return; - } - - int wordsPerRow = BitVectorsForMaxBitCount(width); - int maxBandRows = 0; - long coverStride = (long)width * 2; - if (coverStride > int.MaxValue || - !TryGetBandHeight(width, height, wordsPerRow, coverStride, out maxBandRows)) - { - ThrowInterestBoundsTooLarge(); - } - - int coverStrideInt = (int)coverStride; - float samplingOffsetX = options.SamplingOrigin == RasterizerSamplingOrigin.PixelCenter ? 0.5F : 0F; - - // Create tessellated rings once. Both sequential and parallel paths consume this single - // canonical representation so path flattening/orientation work is never repeated. - using TessellatedMultipolygon multipolygon = TessellatedMultipolygon.Create(path, allocator); - using IMemoryOwner edgeDataOwner = allocator.Allocate(multipolygon.TotalVertexCount); - int edgeCount = BuildEdgeTable(multipolygon, interest.Left, interest.Top, height, samplingOffsetX, edgeDataOwner.Memory.Span); - if (edgeCount <= 0) - { - return; - } - - if (allowParallel && - TryRasterizeParallel( - edgeDataOwner.Memory, - edgeCount, - width, - height, - interest.Top, - wordsPerRow, - coverStrideInt, - maxBandRows, - options.IntersectionRule, - options.RasterizationMode, - allocator, - ref state, - scanlineHandler)) - { - return; - } - - RasterizeSequentialBands( - edgeDataOwner.Memory.Span[..edgeCount], - width, - height, - interest.Top, - wordsPerRow, - coverStrideInt, - maxBandRows, - options.IntersectionRule, - options.RasterizationMode, - allocator, - ref state, - scanlineHandler); - } - - /// - /// Sequential implementation using band buckets over the prebuilt edge table. - /// - /// The caller-owned mutable state type. - /// Prebuilt edges in scanner-local coordinates. - /// Destination width in pixels. - /// Destination height in pixels. - /// Absolute top Y of the interest rectangle. - /// Bit-vector words per row. - /// Cover-area stride in ints. - /// Maximum rows per reusable scratch band. - /// Fill rule. - /// Coverage mode (AA or aliased). - /// Temporary buffer allocator. - /// Caller-owned mutable state. - /// Scanline callback invoked in ascending Y order. - private static void RasterizeSequentialBands( - ReadOnlySpan edges, - int width, - int height, - int interestTop, - int wordsPerRow, - int coverStrideInt, - int maxBandRows, - IntersectionRule intersectionRule, - RasterizationMode rasterizationMode, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - { - int bandHeight = maxBandRows; - int bandCount = (height + bandHeight - 1) / bandHeight; - if (bandCount < 1) - { - return; - } - - using IMemoryOwner bandCountsOwner = allocator.Allocate(bandCount, AllocationOptions.Clean); - Span bandCounts = bandCountsOwner.Memory.Span; - long totalBandEdgeReferences = 0; - for (int i = 0; i < edges.Length; i++) - { - // Each edge can overlap multiple bands. We first count references so we can build - // a compact contiguous index list (CSR-style) without per-band allocations. - int startBand = edges[i].MinRow / bandHeight; - int endBand = edges[i].MaxRow / bandHeight; - totalBandEdgeReferences += (endBand - startBand) + 1; - if (totalBandEdgeReferences > int.MaxValue) - { - ThrowInterestBoundsTooLarge(); - } - - for (int b = startBand; b <= endBand; b++) - { - bandCounts[b]++; - } - } - - int totalReferences = (int)totalBandEdgeReferences; - using IMemoryOwner bandOffsetsOwner = allocator.Allocate(bandCount + 1); - Span bandOffsets = bandOffsetsOwner.Memory.Span; - int offset = 0; - for (int b = 0; b < bandCount; b++) - { - // Prefix sum: bandOffsets[b] is the start index of band b inside bandEdgeReferences. - bandOffsets[b] = offset; - offset += bandCounts[b]; - } - - bandOffsets[bandCount] = offset; - using IMemoryOwner bandWriteCursorOwner = allocator.Allocate(bandCount); - Span bandWriteCursor = bandWriteCursorOwner.Memory.Span; - bandOffsets[..bandCount].CopyTo(bandWriteCursor); - - using IMemoryOwner bandEdgeReferencesOwner = allocator.Allocate(totalReferences); - Span bandEdgeReferences = bandEdgeReferencesOwner.Memory.Span; - for (int edgeIndex = 0; edgeIndex < edges.Length; edgeIndex++) - { - // Scatter each edge index to all bands touched by its row range. - int startBand = edges[edgeIndex].MinRow / bandHeight; - int endBand = edges[edgeIndex].MaxRow / bandHeight; - for (int b = startBand; b <= endBand; b++) - { - bandEdgeReferences[bandWriteCursor[b]++] = edgeIndex; - } - } - - using WorkerScratch scratch = WorkerScratch.Create(allocator, wordsPerRow, coverStrideInt, width, bandHeight); - for (int bandIndex = 0; bandIndex < bandCount; bandIndex++) - { - int bandTop = bandIndex * bandHeight; - int currentBandHeight = Math.Min(bandHeight, height - bandTop); - int start = bandOffsets[bandIndex]; - int length = bandOffsets[bandIndex + 1] - start; - if (length == 0) - { - // No edge crosses this band, so there is nothing to rasterize or clear. - continue; - } - - Context context = scratch.CreateContext(currentBandHeight, intersectionRule, rasterizationMode); - ReadOnlySpan bandEdges = bandEdgeReferences.Slice(start, length); - context.RasterizeEdgeTable(edges, bandEdges, bandTop); - context.EmitScanlines(interestTop + bandTop, scratch.Scanline, ref state, scanlineHandler); - context.ResetTouchedRows(); - } - } - - /// - /// Attempts to execute the tiled parallel scanner. - /// - /// The caller-owned mutable state type. - /// Memory block containing prebuilt edges. - /// Number of valid edges in . - /// Destination width in pixels. - /// Destination height in pixels. - /// Absolute top Y of the interest rectangle. - /// Bit-vector words per row. - /// Cover-area stride in ints. - /// Maximum rows per worker scratch context. - /// Fill rule. - /// Coverage mode (AA or aliased). - /// Temporary buffer allocator. - /// Caller-owned mutable state. - /// Scanline callback invoked in ascending Y order. - /// - /// when the tiled path executed successfully; - /// when the caller should run sequential fallback. - /// - private static bool TryRasterizeParallel( - Memory edgeMemory, - int edgeCount, - int width, - int height, - int interestTop, - int wordsPerRow, - int coverStride, - int maxBandRows, - IntersectionRule intersectionRule, - RasterizationMode rasterizationMode, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - { - int tileHeight = Math.Min(DefaultTileHeight, maxBandRows); - if (tileHeight < 1) - { - return false; - } - - int tileCount = (height + tileHeight - 1) / tileHeight; - if (tileCount == 1) - { - // Tiny workload fast path: avoid bucket construction, worker scheduling, and - // tile-output buffering when everything fits in a single tile. - RasterizeSingleTileDirect( - edgeMemory.Span[..edgeCount], - width, - height, - interestTop, - wordsPerRow, - coverStride, - intersectionRule, - rasterizationMode, - allocator, - ref state, - scanlineHandler); - return true; - } - - if (Environment.ProcessorCount < 2) - { - return false; - } - - long totalPixels = (long)width * height; - if (totalPixels > ParallelOutputPixelBudget) - { - // Parallel mode buffers tile coverage before ordered emission. Skip when the - // buffered output footprint would exceed our safety budget. - return false; - } - - using IMemoryOwner tileCountsOwner = allocator.Allocate(tileCount, AllocationOptions.Clean); - Span tileCounts = tileCountsOwner.Memory.Span; - - long totalTileEdgeReferences = 0; - Span edgeBuffer = edgeMemory.Span; - for (int i = 0; i < edgeCount; i++) - { - // Same CSR construction as sequential mode, now keyed by tile instead of band. - int startTile = edgeBuffer[i].MinRow / tileHeight; - int endTile = edgeBuffer[i].MaxRow / tileHeight; - int tileSpan = (endTile - startTile) + 1; - totalTileEdgeReferences += tileSpan; - - if (totalTileEdgeReferences > int.MaxValue) - { - return false; - } - - for (int t = startTile; t <= endTile; t++) - { - tileCounts[t]++; - } - } - - int totalReferences = (int)totalTileEdgeReferences; - using IMemoryOwner tileOffsetsOwner = allocator.Allocate(tileCount + 1); - Memory tileOffsetsMemory = tileOffsetsOwner.Memory; - Span tileOffsets = tileOffsetsMemory.Span; - - int offset = 0; - for (int t = 0; t < tileCount; t++) - { - // Prefix sum over tile counts so each tile gets one contiguous slice. - tileOffsets[t] = offset; - offset += tileCounts[t]; - } - - tileOffsets[tileCount] = offset; - using IMemoryOwner tileWriteCursorOwner = allocator.Allocate(tileCount); - Span tileWriteCursor = tileWriteCursorOwner.Memory.Span; - tileOffsets[..tileCount].CopyTo(tileWriteCursor); - - using IMemoryOwner tileEdgeReferencesOwner = allocator.Allocate(totalReferences); - Memory tileEdgeReferencesMemory = tileEdgeReferencesOwner.Memory; - Span tileEdgeReferences = tileEdgeReferencesMemory.Span; - - for (int edgeIndex = 0; edgeIndex < edgeCount; edgeIndex++) - { - int startTile = edgeBuffer[edgeIndex].MinRow / tileHeight; - int endTile = edgeBuffer[edgeIndex].MaxRow / tileHeight; - for (int t = startTile; t <= endTile; t++) - { - // Scatter edge indices into each tile's contiguous bucket. - tileEdgeReferences[tileWriteCursor[t]++] = edgeIndex; - } - } - - TileOutput[] tileOutputs = new TileOutput[tileCount]; - ParallelOptions parallelOptions = new() - { - MaxDegreeOfParallelism = Math.Min(Environment.ProcessorCount, tileCount) - }; - - try - { - _ = Parallel.For( - 0, - tileCount, - parallelOptions, - () => WorkerScratch.Create(allocator, wordsPerRow, coverStride, width, tileHeight), - (tileIndex, _, scratch) => - { - ReadOnlySpan edges = edgeMemory.Span[..edgeCount]; - Span tileOffsets = tileOffsetsMemory.Span; - Span tileEdgeReferences = tileEdgeReferencesMemory.Span; - int bandTop = tileIndex * tileHeight; - int bandHeight = Math.Min(tileHeight, height - bandTop); - int start = tileOffsets[tileIndex]; - int length = tileOffsets[tileIndex + 1] - start; - ReadOnlySpan tileEdges = tileEdgeReferences.Slice(start, length); - - // Each tile rasterizes fully independently into worker-local scratch. - RasterizeTile( - scratch, - edges, - tileEdges, - bandTop, - bandHeight, - width, - intersectionRule, - rasterizationMode, - allocator, - tileOutputs, - tileIndex); - - return scratch; - }, - static scratch => scratch.Dispose()); - - EmitTileOutputs(tileOutputs, width, interestTop, ref state, scanlineHandler); - return true; - } - finally - { - foreach (TileOutput output in tileOutputs) - { - output?.Dispose(); - } - } - } - - /// - /// Rasterizes a single tile directly into the caller callback. - /// - /// - /// This avoids parallel setup and tile-output buffering for tiny workloads while preserving - /// the same scan-conversion math and callback ordering as the general tiled path. - /// - /// The caller-owned mutable state type. - /// Prebuilt edge table. - /// Destination width in pixels. - /// Destination height in pixels. - /// Absolute top Y of the interest rectangle. - /// Bit-vector words per row. - /// Cover-area stride in ints. - /// Fill rule. - /// Coverage mode (AA or aliased). - /// Temporary buffer allocator. - /// Caller-owned mutable state. - /// Scanline callback invoked in ascending Y order. - private static void RasterizeSingleTileDirect( - ReadOnlySpan edges, - int width, - int height, - int interestTop, - int wordsPerRow, - int coverStride, - IntersectionRule intersectionRule, - RasterizationMode rasterizationMode, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - { - using WorkerScratch scratch = WorkerScratch.Create(allocator, wordsPerRow, coverStride, width, height); - Context context = scratch.CreateContext(height, intersectionRule, rasterizationMode); - context.RasterizeEdgeTable(edges, bandTop: 0); - context.EmitScanlines(interestTop, scratch.Scanline, ref state, scanlineHandler); - context.ResetTouchedRows(); - } - - /// - /// Rasterizes one tile/band edge subset into temporary coverage buffers. - /// - /// Worker-local scratch buffers. - /// Shared edge table. - /// Indices of edges intersecting this tile. - /// Tile top row in scanner-local coordinates. - /// Tile height in rows. - /// Destination width in pixels. - /// Fill rule. - /// Coverage mode (AA or aliased). - /// Temporary buffer allocator. - /// Output slot array indexed by tile ID. - /// Current tile index. - private static void RasterizeTile( - WorkerScratch scratch, - ReadOnlySpan edges, - ReadOnlySpan tileEdgeIndices, - int bandTop, - int bandHeight, - int width, - IntersectionRule intersectionRule, - RasterizationMode rasterizationMode, - MemoryAllocator allocator, - TileOutput[] outputs, - int tileIndex) - { - if (tileEdgeIndices.Length == 0) - { - return; - } - - Context context = scratch.CreateContext(bandHeight, intersectionRule, rasterizationMode); - context.RasterizeEdgeTable(edges, tileEdgeIndices, bandTop); - - int coverageLength = checked(width * bandHeight); - IMemoryOwner coverageOwner = allocator.Allocate(coverageLength, AllocationOptions.Clean); - IMemoryOwner dirtyRowsOwner = allocator.Allocate(bandHeight, AllocationOptions.Clean); - bool committed = false; - - try - { - TileCaptureState captureState = new(width, coverageOwner.Memory, dirtyRowsOwner.Memory); - - // Emit with destinationTop=0 into tile-local storage; global Y is restored later. - context.EmitScanlines(0, scratch.Scanline, ref captureState, CaptureTileScanline); - outputs[tileIndex] = new TileOutput(bandTop, bandHeight, coverageOwner, dirtyRowsOwner); - committed = true; - } - finally - { - context.ResetTouchedRows(); - - if (!committed) - { - coverageOwner.Dispose(); - dirtyRowsOwner.Dispose(); - } - } - } - - /// - /// Emits buffered tile outputs in deterministic top-to-bottom order. - /// - /// The caller-owned mutable state type. - /// Tile outputs captured by workers. - /// Destination width in pixels. - /// Absolute top Y of the interest rectangle. - /// Caller-owned mutable state. - /// Scanline callback invoked in ascending Y order. - private static void EmitTileOutputs( - TileOutput[] outputs, - int width, - int destinationTop, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - { - foreach (TileOutput output in outputs) - { - if (output is null) - { - continue; - } - - Span coverage = output.CoverageOwner.Memory.Span; - Span dirtyRows = output.DirtyRowsOwner.Memory.Span; - for (int row = 0; row < output.Height; row++) - { - if (dirtyRows[row] == 0) - { - // Rows are sparse; untouched rows were never emitted by the tile worker. - continue; - } - - // Stable top-to-bottom emission keeps observable callback order deterministic. - Span scanline = coverage.Slice(row * width, width); - scanlineHandler(destinationTop + output.Top + row, scanline, ref state); - } - } - } - - /// - /// Captures one emitted scanline into a tile-local output buffer. - /// - /// Row index relative to tile-local coordinates. - /// Coverage scanline. - /// Tile capture state. - private static void CaptureTileScanline(int y, Span scanline, ref TileCaptureState state) - { - // y is tile-local (destinationTop was 0 in RasterizeTile). - int row = y - state.Top; - scanline.CopyTo(state.Coverage.Span.Slice(row * state.Width, state.Width)); - state.DirtyRows.Span[row] = 1; - } - - /// - /// Builds an edge table in scanner-local coordinates. - /// - /// Input tessellated rings. - /// Interest left in absolute coordinates. - /// Interest top in absolute coordinates. - /// Interest height in pixels. - /// Horizontal sampling offset. - /// Destination span for edge records. - /// Number of valid edge records written. - private static int BuildEdgeTable( - TessellatedMultipolygon multipolygon, - int minX, - int minY, - int height, - float samplingOffsetX, - Span destination) - { - int count = 0; - foreach (TessellatedMultipolygon.Ring ring in multipolygon) - { - ReadOnlySpan vertices = ring.Vertices; - for (int i = 0; i < ring.VertexCount; i++) - { - PointF p0 = vertices[i]; - PointF p1 = vertices[i + 1]; - - float x0 = (p0.X - minX) + samplingOffsetX; - float y0 = p0.Y - minY; - float x1 = (p1.X - minX) + samplingOffsetX; - float y1 = p1.Y - minY; - - if (!float.IsFinite(x0) || !float.IsFinite(y0) || !float.IsFinite(x1) || !float.IsFinite(y1)) - { - continue; - } - - if (!ClipToVerticalBounds(ref x0, ref y0, ref x1, ref y1, 0F, height)) - { - continue; - } - - int fx0 = FloatToFixed24Dot8(x0); - int fy0 = FloatToFixed24Dot8(y0); - int fx1 = FloatToFixed24Dot8(x1); - int fy1 = FloatToFixed24Dot8(y1); - if (fy0 == fy1) - { - continue; - } - - ComputeEdgeRowBounds(fy0, fy1, out int minRow, out int maxRow); - destination[count++] = new EdgeData(fx0, fy0, fx1, fy1, minRow, maxRow); - } - } - - return count; - } - - /// - /// Converts bit count to the number of machine words needed to hold the bitset row. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int BitVectorsForMaxBitCount(int maxBitCount) => (maxBitCount + WordBitCount - 1) / WordBitCount; - - /// - /// Calculates the maximum reusable band height under memory and indexing constraints. - /// - /// Interest width. - /// Interest height. - /// Bitset words per row. - /// Cover-area stride in ints. - /// Resulting maximum safe band height. - /// when a valid band height was produced. - private static bool TryGetBandHeight(int width, int height, int wordsPerRow, long coverStride, out int bandHeight) - { - bandHeight = 0; - if (width <= 0 || height <= 0 || wordsPerRow <= 0 || coverStride <= 0) - { - return false; - } - - long bytesPerRow = - ((long)wordsPerRow * nint.Size) + - (coverStride * sizeof(int)) + - sizeof(int); - - long rowsByBudget = BandMemoryBudgetBytes / bytesPerRow; - if (rowsByBudget < 1) - { - rowsByBudget = 1; - } - - long rowsByBitVectors = int.MaxValue / wordsPerRow; - long rowsByCoverArea = int.MaxValue / coverStride; - long maxRows = Math.Min(rowsByBudget, Math.Min(rowsByBitVectors, rowsByCoverArea)); - if (maxRows < 1) - { - return false; - } - - bandHeight = (int)Math.Min(height, maxRows); - return bandHeight > 0; - } - - /// - /// Converts a float coordinate to signed 24.8 fixed-point. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FloatToFixed24Dot8(float value) => (int)MathF.Round(value * FixedOne); - - /// - /// Computes the inclusive row range affected by a clipped non-horizontal edge. - /// - /// Edge start Y in 24.8 fixed-point. - /// Edge end Y in 24.8 fixed-point. - /// First affected integer scan row. - /// Last affected integer scan row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ComputeEdgeRowBounds(int y0, int y1, out int minRow, out int maxRow) - { - int y0Row = y0 >> FixedShift; - int y1Row = y1 >> FixedShift; - - // First touched row is floor(min(y0, y1)). - minRow = y0Row < y1Row ? y0Row : y1Row; - - int y0Fraction = y0 & (FixedOne - 1); - int y1Fraction = y1 & (FixedOne - 1); - - // Last touched row is ceil(max(y)) - 1: - // - when fractional part is non-zero, row is unchanged; - // - when exactly on a row boundary, subtract 1 (edge ownership rule). - int y0Candidate = y0Row - (((y0Fraction - 1) >> 31) & 1); - int y1Candidate = y1Row - (((y1Fraction - 1) >> 31) & 1); - maxRow = y0Candidate > y1Candidate ? y0Candidate : y1Candidate; - } - - /// - /// Clips a fixed-point segment against vertical bounds. - /// - /// Segment start X in 24.8 fixed-point (updated in place). - /// Segment start Y in 24.8 fixed-point (updated in place). - /// Segment end X in 24.8 fixed-point (updated in place). - /// Segment end Y in 24.8 fixed-point (updated in place). - /// Minimum Y bound in 24.8 fixed-point. - /// Maximum Y bound in 24.8 fixed-point. - /// when a non-horizontal clipped segment remains. - private static bool ClipToVerticalBoundsFixed(ref int x0, ref int y0, ref int x1, ref int y1, int minY, int maxY) - { - double t0 = 0D; - double t1 = 1D; - int originX0 = x0; - int originY0 = y0; - long dx = (long)x1 - originX0; - long dy = (long)y1 - originY0; - if (!ClipTestFixed(-(double)dy, originY0 - (double)minY, ref t0, ref t1)) - { - return false; - } - - if (!ClipTestFixed(dy, maxY - (double)originY0, ref t0, ref t1)) - { - return false; - } - - if (t1 < 1D) - { - x1 = originX0 + (int)Math.Round(dx * t1); - y1 = originY0 + (int)Math.Round(dy * t1); - } - - if (t0 > 0D) - { - x0 = originX0 + (int)Math.Round(dx * t0); - y0 = originY0 + (int)Math.Round(dy * t0); - } - - return y0 != y1; - } - - /// - /// Clips a segment against vertical bounds using Liang-Barsky style parametric tests. - /// - /// Segment start X (updated in place). - /// Segment start Y (updated in place). - /// Segment end X (updated in place). - /// Segment end Y (updated in place). - /// Minimum Y bound. - /// Maximum Y bound. - /// when a non-horizontal clipped segment remains. - private static bool ClipToVerticalBounds(ref float x0, ref float y0, ref float x1, ref float y1, float minY, float maxY) - { - float t0 = 0F; - float t1 = 1F; - float dx = x1 - x0; - float dy = y1 - y0; - - if (!ClipTest(-dy, y0 - minY, ref t0, ref t1)) - { - return false; - } - - if (!ClipTest(dy, maxY - y0, ref t0, ref t1)) - { - return false; - } - - if (t1 < 1F) - { - x1 = x0 + (dx * t1); - y1 = y0 + (dy * t1); - } - - if (t0 > 0F) - { - x0 += dx * t0; - y0 += dy * t0; - } - - return y0 != y1; - } - - /// - /// One Liang-Barsky clip test step. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool ClipTest(float p, float q, ref float t0, ref float t1) - { - if (p == 0F) - { - return q >= 0F; - } - - float r = q / p; - if (p < 0F) - { - if (r > t1) - { - return false; - } - - if (r > t0) - { - t0 = r; - } - } - else - { - if (r < t0) - { - return false; - } - - if (r < t1) - { - t1 = r; - } - } - - return true; - } - - /// - /// One Liang-Barsky clip test step for fixed-point clipping. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool ClipTestFixed(double p, double q, ref double t0, ref double t1) - { - if (p == 0D) - { - return q >= 0D; - } - - double r = q / p; - if (p < 0D) - { - if (r > t1) - { - return false; - } - - if (r > t0) - { - t0 = r; - } - } - else - { - if (r < t0) - { - return false; - } - - if (r < t1) - { - t1 = r; - } - } - - return true; - } - - /// - /// Returns one when a fixed-point value lies exactly on a cell boundary at or below zero. - /// This is used to keep edge ownership consistent for vertical lines. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FindAdjustment(int value) - { - int lte0 = ~((value - 1) >> 31) & 1; - int divisibleBy256 = (((value & (FixedOne - 1)) - 1) >> 31) & 1; - return lte0 & divisibleBy256; - } - - /// - /// Machine-word trailing zero count used for sparse bitset iteration. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int TrailingZeroCount(nuint value) - => nint.Size == sizeof(ulong) - ? BitOperations.TrailingZeroCount((ulong)value) - : BitOperations.TrailingZeroCount((uint)value); - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowInterestBoundsTooLarge() - => throw new ImageProcessingException("The rasterizer interest bounds are too large for PolygonScanner buffers."); - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowBandHeightExceedsScratchCapacity() - => throw new ImageProcessingException("Requested band height exceeds worker scratch capacity."); - - /// - /// Band/tile-local scanner context that owns mutable coverage accumulation state. - /// - /// - /// Instances are intentionally stack-bound to keep hot-path data in spans and avoid heap churn. - /// - private ref struct Context - { - private readonly Span bitVectors; - private readonly Span coverArea; - private readonly Span startCover; - private readonly Span rowHasBits; - private readonly Span rowTouched; - private readonly Span touchedRows; - private readonly int width; - private readonly int height; - private readonly int wordsPerRow; - private readonly int coverStride; - private readonly IntersectionRule intersectionRule; - private readonly RasterizationMode rasterizationMode; - private int touchedRowCount; - - /// - /// Initializes a new instance of the struct. - /// - public Context( - Span bitVectors, - Span coverArea, - Span startCover, - Span rowHasBits, - Span rowTouched, - Span touchedRows, - int width, - int height, - int wordsPerRow, - int coverStride, - IntersectionRule intersectionRule, - RasterizationMode rasterizationMode) - { - this.bitVectors = bitVectors; - this.coverArea = coverArea; - this.startCover = startCover; - this.rowHasBits = rowHasBits; - this.rowTouched = rowTouched; - this.touchedRows = touchedRows; - this.width = width; - this.height = height; - this.wordsPerRow = wordsPerRow; - this.coverStride = coverStride; - this.intersectionRule = intersectionRule; - this.rasterizationMode = rasterizationMode; - this.touchedRowCount = 0; - } - - /// - /// Rasterizes all edges in a tessellated multipolygon directly into this context. - /// - /// Input tessellated rings. - /// Absolute left coordinate of the current scanner window. - /// Absolute top coordinate of the current scanner window. - /// Horizontal sample origin offset. - public void RasterizeMultipolygon(TessellatedMultipolygon multipolygon, int minX, int minY, float samplingOffsetX) - { - foreach (TessellatedMultipolygon.Ring ring in multipolygon) - { - ReadOnlySpan vertices = ring.Vertices; - for (int i = 0; i < ring.VertexCount; i++) - { - PointF p0 = vertices[i]; - PointF p1 = vertices[i + 1]; - - float x0 = (p0.X - minX) + samplingOffsetX; - float y0 = p0.Y - minY; - float x1 = (p1.X - minX) + samplingOffsetX; - float y1 = p1.Y - minY; - - if (!float.IsFinite(x0) || !float.IsFinite(y0) || !float.IsFinite(x1) || !float.IsFinite(y1)) - { - continue; - } - - if (!ClipToVerticalBounds(ref x0, ref y0, ref x1, ref y1, 0F, this.height)) - { - continue; - } - - int fx0 = FloatToFixed24Dot8(x0); - int fy0 = FloatToFixed24Dot8(y0); - int fx1 = FloatToFixed24Dot8(x1); - int fy1 = FloatToFixed24Dot8(y1); - if (fy0 == fy1) - { - continue; - } - - this.RasterizeLine(fx0, fy0, fx1, fy1); - } - } - } - - /// - /// Rasterizes all prebuilt edges that overlap this context. - /// - /// Shared edge table. - /// Top row of this context in global scanner-local coordinates. - public void RasterizeEdgeTable(ReadOnlySpan edges, int bandTop) - { - int bandTopFixed = bandTop * FixedOne; - int bandBottomFixed = bandTopFixed + (this.height * FixedOne); - - for (int i = 0; i < edges.Length; i++) - { - EdgeData edge = edges[i]; - int x0 = edge.X0; - int y0 = edge.Y0; - int x1 = edge.X1; - int y1 = edge.Y1; - - if (!ClipToVerticalBoundsFixed(ref x0, ref y0, ref x1, ref y1, bandTopFixed, bandBottomFixed)) - { - continue; - } - - // Convert global scanner Y to band-local Y after clipping. - y0 -= bandTopFixed; - y1 -= bandTopFixed; - - this.RasterizeLine(x0, y0, x1, y1); - } - } - - /// - /// Rasterizes a subset of prebuilt edges that intersect this context's vertical range. - /// - /// Shared edge table. - /// Indices into for this band/tile. - /// Top row of this context in global scanner-local coordinates. - public void RasterizeEdgeTable(ReadOnlySpan edges, ReadOnlySpan edgeIndices, int bandTop) - { - int bandTopFixed = bandTop * FixedOne; - int bandBottomFixed = bandTopFixed + (this.height * FixedOne); - - for (int i = 0; i < edgeIndices.Length; i++) - { - EdgeData edge = edges[edgeIndices[i]]; - int x0 = edge.X0; - int y0 = edge.Y0; - int x1 = edge.X1; - int y1 = edge.Y1; - - if (!ClipToVerticalBoundsFixed(ref x0, ref y0, ref x1, ref y1, bandTopFixed, bandBottomFixed)) - { - continue; - } - - // Convert global scanner Y to band-local Y after clipping. - y0 -= bandTopFixed; - y1 -= bandTopFixed; - - this.RasterizeLine(x0, y0, x1, y1); - } - } - - /// - /// Converts accumulated cover/area tables into scanline coverage callbacks. - /// - /// The caller-owned mutable state type. - /// Absolute destination Y corresponding to row zero in this context. - /// Reusable scanline scratch buffer. - /// Caller-owned mutable state. - /// Scanline callback invoked in ascending Y order. - public readonly void EmitScanlines(int destinationTop, Span scanline, ref TState state, RasterizerScanlineHandler scanlineHandler) - where TState : struct - { - for (int row = 0; row < this.height; row++) - { - int rowCover = this.startCover[row]; - if (rowCover == 0 && this.rowHasBits[row] == 0) - { - // Nothing contributed to this row. - continue; - } - - Span rowBitVectors = this.bitVectors.Slice(row * this.wordsPerRow, this.wordsPerRow); - scanline.Clear(); - bool scanlineDirty = this.EmitRowCoverage(rowBitVectors, row, rowCover, scanline); - if (scanlineDirty) - { - scanlineHandler(destinationTop + row, scanline, ref state); - } - } - } - - /// - /// Clears only rows touched during the previous rasterization pass. - /// - /// - /// This sparse reset strategy avoids clearing full scratch buffers when geometry is sparse. - /// - public void ResetTouchedRows() - { - // Reset only rows that received contributions in this band. This avoids clearing - // full temporary buffers when geometry is sparse relative to the interest bounds. - for (int i = 0; i < this.touchedRowCount; i++) - { - int row = this.touchedRows[i]; - this.startCover[row] = 0; - this.rowTouched[row] = 0; - - if (this.rowHasBits[row] == 0) - { - continue; - } - - this.rowHasBits[row] = 0; - this.bitVectors.Slice(row * this.wordsPerRow, this.wordsPerRow).Clear(); - } - - this.touchedRowCount = 0; - } - - /// - /// Emits one row by iterating touched columns and coalescing equal-coverage spans. - /// - /// Bitset words indicating touched columns in this row. - /// Row index inside the context. - /// Initial carry cover value from x less than zero contributions. - /// Destination scanline coverage buffer. - /// when at least one non-zero span was emitted. - private readonly bool EmitRowCoverage(ReadOnlySpan rowBitVectors, int row, int cover, Span scanline) - { - int rowOffset = row * this.coverStride; - int spanStart = 0; - int spanEnd = 0; - float spanCoverage = 0F; - bool hasCoverage = false; - - for (int wordIndex = 0; wordIndex < rowBitVectors.Length; wordIndex++) - { - // Iterate touched columns sparsely by scanning set bits only. - nuint bitset = rowBitVectors[wordIndex]; - while (bitset != 0) - { - int localBitIndex = TrailingZeroCount(bitset); - bitset &= bitset - 1; - - int x = (wordIndex * WordBitCount) + localBitIndex; - if ((uint)x >= (uint)this.width) - { - continue; - } - - int tableIndex = rowOffset + (x << 1); - - // Area uses current cover before adding this cell's delta. This matches - // scan-conversion math where area integrates the edge state at cell entry. - int area = this.coverArea[tableIndex + 1] + (cover << AreaToCoverageShift); - float coverage = this.AreaToCoverage(area); - - if (spanEnd == x) - { - if (coverage <= 0F) - { - hasCoverage |= FlushSpan(scanline, spanStart, spanEnd, spanCoverage); - spanStart = x + 1; - spanEnd = spanStart; - spanCoverage = 0F; - } - else if (coverage == spanCoverage) - { - spanEnd = x + 1; - } - else - { - hasCoverage |= FlushSpan(scanline, spanStart, spanEnd, spanCoverage); - spanStart = x; - spanEnd = x + 1; - spanCoverage = coverage; - } - } - else - { - // We jumped over untouched columns. If cover != 0 the gap has a constant - // non-zero coverage and must be emitted as its own run. - if (cover == 0) - { - hasCoverage |= FlushSpan(scanline, spanStart, spanEnd, spanCoverage); - spanStart = x; - spanEnd = x + 1; - spanCoverage = coverage; - } - else - { - float gapCoverage = this.AreaToCoverage(cover << AreaToCoverageShift); - if (spanCoverage == gapCoverage) - { - if (coverage == gapCoverage) - { - spanEnd = x + 1; - } - else - { - hasCoverage |= FlushSpan(scanline, spanStart, x, spanCoverage); - spanStart = x; - spanEnd = x + 1; - spanCoverage = coverage; - } - } - else - { - hasCoverage |= FlushSpan(scanline, spanStart, spanEnd, spanCoverage); - hasCoverage |= FlushSpan(scanline, spanEnd, x, gapCoverage); - spanStart = x; - spanEnd = x + 1; - spanCoverage = coverage; - } - } - } - - cover += this.coverArea[tableIndex]; - } - } - - // Flush tail run and any remaining constant-cover tail after the last touched cell. - hasCoverage |= FlushSpan(scanline, spanStart, spanEnd, spanCoverage); - if (cover != 0 && spanEnd < this.width) - { - hasCoverage |= FlushSpan(scanline, spanEnd, this.width, this.AreaToCoverage(cover << AreaToCoverageShift)); - } - - return hasCoverage; - } - - /// - /// Converts accumulated signed area to normalized coverage under the selected fill rule. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly float AreaToCoverage(int area) - { - int signedArea = area >> AreaToCoverageShift; - int absoluteArea = signedArea < 0 ? -signedArea : signedArea; - float coverage; - if (this.intersectionRule == IntersectionRule.NonZero) - { - // Non-zero winding clamps absolute winding accumulation to [0, 1]. - if (absoluteArea >= CoverageStepCount) - { - coverage = 1F; - } - else - { - coverage = absoluteArea * CoverageScale; - } - } - else - { - // Even-odd wraps every 2*CoverageStepCount and mirrors second half. - int wrapped = absoluteArea & EvenOddMask; - if (wrapped > CoverageStepCount) - { - wrapped = EvenOddPeriod - wrapped; - } - - coverage = wrapped >= CoverageStepCount ? 1F : wrapped * CoverageScale; - } - - if (this.rasterizationMode == RasterizationMode.Aliased) - { - // Aliased mode quantizes final coverage to hard 0/1 per pixel. - return coverage >= 0.5F ? 1F : 0F; - } - - return coverage; - } - - /// - /// Writes one coverage span into the scanline buffer. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool FlushSpan(Span scanline, int start, int end, float coverage) - { - if (coverage <= 0F || end <= start) - { - return false; - } - - scanline[start..end].Fill(coverage); - return true; - } - - /// - /// Sets a row/column bit and reports whether it was newly set. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly bool ConditionalSetBit(int row, int column) - { - int bitIndex = row * this.wordsPerRow; - int wordIndex = bitIndex + (column / WordBitCount); - nuint mask = (nuint)1 << (column % WordBitCount); - ref nuint word = ref this.bitVectors[wordIndex]; - bool newlySet = (word & mask) == 0; - word |= mask; - - // Fast row-level early-out for EmitScanlines. - this.rowHasBits[row] = 1; - return newlySet; - } - - /// - /// Adds one cell contribution into cover/area accumulators. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AddCell(int row, int column, int delta, int area) - { - if ((uint)row >= (uint)this.height) - { - return; - } - - this.MarkRowTouched(row); - - if (column < 0) - { - // Contributions left of x=0 accumulate into the row carry. - this.startCover[row] += delta; - return; - } - - if ((uint)column >= (uint)this.width) - { - return; - } - - int index = (row * this.coverStride) + (column << 1); - if (this.ConditionalSetBit(row, column)) - { - // First write wins initialization path avoids reading old values. - this.coverArea[index] = delta; - this.coverArea[index + 1] = area; - } - else - { - // Multiple edges can hit the same cell; accumulate signed values. - this.coverArea[index] += delta; - this.coverArea[index + 1] += area; - } - } - - /// - /// Marks a row as touched once so sparse reset can clear it later. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void MarkRowTouched(int row) - { - if (this.rowTouched[row] != 0) - { - return; - } - - this.rowTouched[row] = 1; - this.touchedRows[this.touchedRowCount++] = row; - } - - /// - /// Emits one vertical cell contribution. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CellVertical(int px, int py, int x, int y0, int y1) - { - int delta = y0 - y1; - int area = delta * ((FixedOne * 2) - x - x); - this.AddCell(py, px, delta, area); - } - - /// - /// Emits one general cell contribution. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Cell(int row, int px, int x0, int y0, int x1, int y1) - { - int delta = y0 - y1; - int area = delta * ((FixedOne * 2) - x0 - x1); - this.AddCell(row, px, delta, area); - } - - /// - /// Rasterizes a downward vertical edge segment. - /// - private void VerticalDown(int columnIndex, int y0, int y1, int x) - { - int rowIndex0 = y0 >> FixedShift; - int rowIndex1 = (y1 - 1) >> FixedShift; - int fy0 = y0 - (rowIndex0 << FixedShift); - int fy1 = y1 - (rowIndex1 << FixedShift); - int fx = x - (columnIndex << FixedShift); - - if (rowIndex0 == rowIndex1) - { - // Entire segment stays within one row. - this.CellVertical(columnIndex, rowIndex0, fx, fy0, fy1); - return; - } - - // First partial row, full middle rows, last partial row. - this.CellVertical(columnIndex, rowIndex0, fx, fy0, FixedOne); - for (int row = rowIndex0 + 1; row < rowIndex1; row++) - { - this.CellVertical(columnIndex, row, fx, 0, FixedOne); - } - - this.CellVertical(columnIndex, rowIndex1, fx, 0, fy1); - } - - /// - /// Rasterizes an upward vertical edge segment. - /// - private void VerticalUp(int columnIndex, int y0, int y1, int x) - { - int rowIndex0 = (y0 - 1) >> FixedShift; - int rowIndex1 = y1 >> FixedShift; - int fy0 = y0 - (rowIndex0 << FixedShift); - int fy1 = y1 - (rowIndex1 << FixedShift); - int fx = x - (columnIndex << FixedShift); - - if (rowIndex0 == rowIndex1) - { - // Entire segment stays within one row. - this.CellVertical(columnIndex, rowIndex0, fx, fy0, fy1); - return; - } - - // First partial row, full middle rows, last partial row (upward direction). - this.CellVertical(columnIndex, rowIndex0, fx, fy0, 0); - for (int row = rowIndex0 - 1; row > rowIndex1; row--) - { - this.CellVertical(columnIndex, row, fx, FixedOne, 0); - } - - this.CellVertical(columnIndex, rowIndex1, fx, FixedOne, fy1); - } - - // The following row/line helpers are directional variants of the same fixed-point edge - // walker. They are intentionally split to minimize branch costs in hot loops. - - /// - /// Rasterizes a downward, left-to-right segment within a single row. - /// - private void RowDownR(int rowIndex, int p0x, int p0y, int p1x, int p1y) - { - int columnIndex0 = p0x >> FixedShift; - int columnIndex1 = (p1x - 1) >> FixedShift; - int fx0 = p0x - (columnIndex0 << FixedShift); - int fx1 = p1x - (columnIndex1 << FixedShift); - - if (columnIndex0 == columnIndex1) - { - this.Cell(rowIndex, columnIndex0, fx0, p0y, fx1, p1y); - return; - } - - int dx = p1x - p0x; - int dy = p1y - p0y; - int pp = (FixedOne - fx0) * dy; - int cy = p0y + (pp / dx); - - this.Cell(rowIndex, columnIndex0, fx0, p0y, FixedOne, cy); - - int idx = columnIndex0 + 1; - if (idx != columnIndex1) - { - int mod = (pp % dx) - dx; - int p = FixedOne * dy; - int lift = p / dx; - int rem = p % dx; - - for (; idx != columnIndex1; idx++) - { - int delta = lift; - mod += rem; - if (mod >= 0) - { - mod -= dx; - delta++; - } - - int ny = cy + delta; - this.Cell(rowIndex, idx, 0, cy, FixedOne, ny); - cy = ny; - } - } - - this.Cell(rowIndex, columnIndex1, 0, cy, fx1, p1y); - } - - /// - /// RowDownR variant that handles perfectly vertical edge ownership consistently. - /// - private void RowDownR_V(int rowIndex, int p0x, int p0y, int p1x, int p1y) - { - if (p0x < p1x) - { - this.RowDownR(rowIndex, p0x, p0y, p1x, p1y); - } - else - { - int columnIndex = (p0x - FindAdjustment(p0x)) >> FixedShift; - int x = p0x - (columnIndex << FixedShift); - this.CellVertical(columnIndex, rowIndex, x, p0y, p1y); - } - } - - /// - /// Rasterizes an upward, left-to-right segment within a single row. - /// - private void RowUpR(int rowIndex, int p0x, int p0y, int p1x, int p1y) - { - int columnIndex0 = p0x >> FixedShift; - int columnIndex1 = (p1x - 1) >> FixedShift; - int fx0 = p0x - (columnIndex0 << FixedShift); - int fx1 = p1x - (columnIndex1 << FixedShift); - - if (columnIndex0 == columnIndex1) - { - this.Cell(rowIndex, columnIndex0, fx0, p0y, fx1, p1y); - return; - } - - int dx = p1x - p0x; - int dy = p0y - p1y; - int pp = (FixedOne - fx0) * dy; - int cy = p0y - (pp / dx); - - this.Cell(rowIndex, columnIndex0, fx0, p0y, FixedOne, cy); - - int idx = columnIndex0 + 1; - if (idx != columnIndex1) - { - int mod = (pp % dx) - dx; - int p = FixedOne * dy; - int lift = p / dx; - int rem = p % dx; - - for (; idx != columnIndex1; idx++) - { - int delta = lift; - mod += rem; - if (mod >= 0) - { - mod -= dx; - delta++; - } - - int ny = cy - delta; - this.Cell(rowIndex, idx, 0, cy, FixedOne, ny); - cy = ny; - } - } - - this.Cell(rowIndex, columnIndex1, 0, cy, fx1, p1y); - } - - /// - /// RowUpR variant that handles perfectly vertical edge ownership consistently. - /// - private void RowUpR_V(int rowIndex, int p0x, int p0y, int p1x, int p1y) - { - if (p0x < p1x) - { - this.RowUpR(rowIndex, p0x, p0y, p1x, p1y); - } - else - { - int columnIndex = (p0x - FindAdjustment(p0x)) >> FixedShift; - int x = p0x - (columnIndex << FixedShift); - this.CellVertical(columnIndex, rowIndex, x, p0y, p1y); - } - } - - /// - /// Rasterizes a downward, right-to-left segment within a single row. - /// - private void RowDownL(int rowIndex, int p0x, int p0y, int p1x, int p1y) - { - int columnIndex0 = (p0x - 1) >> FixedShift; - int columnIndex1 = p1x >> FixedShift; - int fx0 = p0x - (columnIndex0 << FixedShift); - int fx1 = p1x - (columnIndex1 << FixedShift); - - if (columnIndex0 == columnIndex1) - { - this.Cell(rowIndex, columnIndex0, fx0, p0y, fx1, p1y); - return; - } - - int dx = p0x - p1x; - int dy = p1y - p0y; - int pp = fx0 * dy; - int cy = p0y + (pp / dx); - - this.Cell(rowIndex, columnIndex0, fx0, p0y, 0, cy); - - int idx = columnIndex0 - 1; - if (idx != columnIndex1) - { - int mod = (pp % dx) - dx; - int p = FixedOne * dy; - int lift = p / dx; - int rem = p % dx; - - for (; idx != columnIndex1; idx--) - { - int delta = lift; - mod += rem; - if (mod >= 0) - { - mod -= dx; - delta++; - } - - int ny = cy + delta; - this.Cell(rowIndex, idx, FixedOne, cy, 0, ny); - cy = ny; - } - } - - this.Cell(rowIndex, columnIndex1, FixedOne, cy, fx1, p1y); - } - - /// - /// RowDownL variant that handles perfectly vertical edge ownership consistently. - /// - private void RowDownL_V(int rowIndex, int p0x, int p0y, int p1x, int p1y) - { - if (p0x > p1x) - { - this.RowDownL(rowIndex, p0x, p0y, p1x, p1y); - } - else - { - int columnIndex = (p0x - FindAdjustment(p0x)) >> FixedShift; - int x = p0x - (columnIndex << FixedShift); - this.CellVertical(columnIndex, rowIndex, x, p0y, p1y); - } - } - - /// - /// Rasterizes an upward, right-to-left segment within a single row. - /// - private void RowUpL(int rowIndex, int p0x, int p0y, int p1x, int p1y) - { - int columnIndex0 = (p0x - 1) >> FixedShift; - int columnIndex1 = p1x >> FixedShift; - int fx0 = p0x - (columnIndex0 << FixedShift); - int fx1 = p1x - (columnIndex1 << FixedShift); - - if (columnIndex0 == columnIndex1) - { - this.Cell(rowIndex, columnIndex0, fx0, p0y, fx1, p1y); - return; - } - - int dx = p0x - p1x; - int dy = p0y - p1y; - int pp = fx0 * dy; - int cy = p0y - (pp / dx); - - this.Cell(rowIndex, columnIndex0, fx0, p0y, 0, cy); - - int idx = columnIndex0 - 1; - if (idx != columnIndex1) - { - int mod = (pp % dx) - dx; - int p = FixedOne * dy; - int lift = p / dx; - int rem = p % dx; - - for (; idx != columnIndex1; idx--) - { - int delta = lift; - mod += rem; - if (mod >= 0) - { - mod -= dx; - delta++; - } - - int ny = cy - delta; - this.Cell(rowIndex, idx, FixedOne, cy, 0, ny); - cy = ny; - } - } - - this.Cell(rowIndex, columnIndex1, FixedOne, cy, fx1, p1y); - } - - /// - /// RowUpL variant that handles perfectly vertical edge ownership consistently. - /// - private void RowUpL_V(int rowIndex, int p0x, int p0y, int p1x, int p1y) - { - if (p0x > p1x) - { - this.RowUpL(rowIndex, p0x, p0y, p1x, p1y); - } - else - { - int columnIndex = (p0x - FindAdjustment(p0x)) >> FixedShift; - int x = p0x - (columnIndex << FixedShift); - this.CellVertical(columnIndex, rowIndex, x, p0y, p1y); - } - } - - /// - /// Rasterizes a downward, left-to-right segment spanning multiple rows. - /// - private void LineDownR(int rowIndex0, int rowIndex1, int x0, int y0, int x1, int y1) - { - int dx = x1 - x0; - int dy = y1 - y0; - int fy0 = y0 - (rowIndex0 << FixedShift); - int fy1 = y1 - (rowIndex1 << FixedShift); - - // p/delta/mod/rem implement an integer DDA that advances x at row boundaries - // without per-row floating-point math. - int p = (FixedOne - fy0) * dx; - int delta = p / dy; - int cx = x0 + delta; - - this.RowDownR_V(rowIndex0, x0, fy0, cx, FixedOne); - - int row = rowIndex0 + 1; - if (row != rowIndex1) - { - int mod = (p % dy) - dy; - p = FixedOne * dx; - int lift = p / dy; - int rem = p % dy; - - for (; row != rowIndex1; row++) - { - delta = lift; - mod += rem; - if (mod >= 0) - { - mod -= dy; - delta++; - } - - int nx = cx + delta; - this.RowDownR_V(row, cx, 0, nx, FixedOne); - cx = nx; - } - } - - this.RowDownR_V(rowIndex1, cx, 0, x1, fy1); - } - - /// - /// Rasterizes an upward, left-to-right segment spanning multiple rows. - /// - private void LineUpR(int rowIndex0, int rowIndex1, int x0, int y0, int x1, int y1) - { - int dx = x1 - x0; - int dy = y0 - y1; - int fy0 = y0 - (rowIndex0 << FixedShift); - int fy1 = y1 - (rowIndex1 << FixedShift); - - // Upward version of the same integer DDA stepping as LineDownR. - int p = fy0 * dx; - int delta = p / dy; - int cx = x0 + delta; - - this.RowUpR_V(rowIndex0, x0, fy0, cx, 0); - - int row = rowIndex0 - 1; - if (row != rowIndex1) - { - int mod = (p % dy) - dy; - p = FixedOne * dx; - int lift = p / dy; - int rem = p % dy; - - for (; row != rowIndex1; row--) - { - delta = lift; - mod += rem; - if (mod >= 0) - { - mod -= dy; - delta++; - } - - int nx = cx + delta; - this.RowUpR_V(row, cx, FixedOne, nx, 0); - cx = nx; - } - } - - this.RowUpR_V(rowIndex1, cx, FixedOne, x1, fy1); - } - - /// - /// Rasterizes a downward, right-to-left segment spanning multiple rows. - /// - private void LineDownL(int rowIndex0, int rowIndex1, int x0, int y0, int x1, int y1) - { - int dx = x0 - x1; - int dy = y1 - y0; - int fy0 = y0 - (rowIndex0 << FixedShift); - int fy1 = y1 - (rowIndex1 << FixedShift); - - // Right-to-left variant of the integer DDA. - int p = (FixedOne - fy0) * dx; - int delta = p / dy; - int cx = x0 - delta; - - this.RowDownL_V(rowIndex0, x0, fy0, cx, FixedOne); - - int row = rowIndex0 + 1; - if (row != rowIndex1) - { - int mod = (p % dy) - dy; - p = FixedOne * dx; - int lift = p / dy; - int rem = p % dy; - - for (; row != rowIndex1; row++) - { - delta = lift; - mod += rem; - if (mod >= 0) - { - mod -= dy; - delta++; - } - - int nx = cx - delta; - this.RowDownL_V(row, cx, 0, nx, FixedOne); - cx = nx; - } - } - - this.RowDownL_V(rowIndex1, cx, 0, x1, fy1); - } - - /// - /// Rasterizes an upward, right-to-left segment spanning multiple rows. - /// - private void LineUpL(int rowIndex0, int rowIndex1, int x0, int y0, int x1, int y1) - { - int dx = x0 - x1; - int dy = y0 - y1; - int fy0 = y0 - (rowIndex0 << FixedShift); - int fy1 = y1 - (rowIndex1 << FixedShift); - - // Upward + right-to-left variant of the integer DDA. - int p = fy0 * dx; - int delta = p / dy; - int cx = x0 - delta; - - this.RowUpL_V(rowIndex0, x0, fy0, cx, 0); - - int row = rowIndex0 - 1; - if (row != rowIndex1) - { - int mod = (p % dy) - dy; - p = FixedOne * dx; - int lift = p / dy; - int rem = p % dy; - - for (; row != rowIndex1; row--) - { - delta = lift; - mod += rem; - if (mod >= 0) - { - mod -= dy; - delta++; - } - - int nx = cx - delta; - this.RowUpL_V(row, cx, FixedOne, nx, 0); - cx = nx; - } - } - - this.RowUpL_V(rowIndex1, cx, FixedOne, x1, fy1); - } - - /// - /// Dispatches a clipped edge to the correct directional fixed-point walker. - /// - private void RasterizeLine(int x0, int y0, int x1, int y1) - { - if (x0 == x1) - { - // Vertical edges need ownership adjustment to avoid double counting at cell seams. - int columnIndex = (x0 - FindAdjustment(x0)) >> FixedShift; - if (y0 < y1) - { - this.VerticalDown(columnIndex, y0, y1, x0); - } - else - { - this.VerticalUp(columnIndex, y0, y1, x0); - } - - return; - } - - if (y0 < y1) - { - // Downward edges use inclusive top/exclusive bottom row mapping. - int rowIndex0 = y0 >> FixedShift; - int rowIndex1 = (y1 - 1) >> FixedShift; - if (rowIndex0 == rowIndex1) - { - int rowBase = rowIndex0 << FixedShift; - int localY0 = y0 - rowBase; - int localY1 = y1 - rowBase; - if (x0 < x1) - { - this.RowDownR(rowIndex0, x0, localY0, x1, localY1); - } - else - { - this.RowDownL(rowIndex0, x0, localY0, x1, localY1); - } - } - else if (x0 < x1) - { - this.LineDownR(rowIndex0, rowIndex1, x0, y0, x1, y1); - } - else - { - this.LineDownL(rowIndex0, rowIndex1, x0, y0, x1, y1); - } - - return; - } - - // Upward edges mirror the mapping to preserve winding consistency. - int upRowIndex0 = (y0 - 1) >> FixedShift; - int upRowIndex1 = y1 >> FixedShift; - if (upRowIndex0 == upRowIndex1) - { - int rowBase = upRowIndex0 << FixedShift; - int localY0 = y0 - rowBase; - int localY1 = y1 - rowBase; - if (x0 < x1) - { - this.RowUpR(upRowIndex0, x0, localY0, x1, localY1); - } - else - { - this.RowUpL(upRowIndex0, x0, localY0, x1, localY1); - } - } - else if (x0 < x1) - { - this.LineUpR(upRowIndex0, upRowIndex1, x0, y0, x1, y1); - } - else - { - this.LineUpL(upRowIndex0, upRowIndex1, x0, y0, x1, y1); - } - } - } - - /// - /// Immutable scanner-local edge record with precomputed affected-row bounds. - /// - /// - /// All coordinates are stored as signed 24.8 fixed-point integers for predictable hot-path - /// access without per-read unpacking. - /// - private readonly struct EdgeData - { - /// - /// Gets edge start X in scanner-local coordinates (24.8 fixed-point). - /// - public readonly int X0; - - /// - /// Gets edge start Y in scanner-local coordinates (24.8 fixed-point). - /// - public readonly int Y0; - - /// - /// Gets edge end X in scanner-local coordinates (24.8 fixed-point). - /// - public readonly int X1; - - /// - /// Gets edge end Y in scanner-local coordinates (24.8 fixed-point). - /// - public readonly int Y1; - - /// - /// Gets the first scanner row affected by this edge. - /// - public readonly int MinRow; - - /// - /// Gets the last scanner row affected by this edge. - /// - public readonly int MaxRow; - - /// - /// Initializes a new instance of the struct. - /// - public EdgeData(int x0, int y0, int x1, int y1, int minRow, int maxRow) - { - this.X0 = x0; - this.Y0 = y0; - this.X1 = x1; - this.Y1 = y1; - this.MinRow = minRow; - this.MaxRow = maxRow; - } - } - - /// - /// Mutable state used while capturing one tile's emitted scanlines. - /// - private readonly struct TileCaptureState - { - /// - /// Initializes a new instance of the struct. - /// - public TileCaptureState(int width, Memory coverage, Memory dirtyRows) - { - this.Top = 0; - this.Width = width; - this.Coverage = coverage; - this.DirtyRows = dirtyRows; - } - - /// - /// Gets the row origin of this capture buffer. - /// - public int Top { get; } - - /// - /// Gets the scanline width. - /// - public int Width { get; } - - /// - /// Gets contiguous tile coverage storage. - /// - public Memory Coverage { get; } - - /// - /// Gets per-row dirty flags for sparse output emission. - /// - public Memory DirtyRows { get; } - } - - /// - /// Buffered output produced by one rasterized tile. - /// - private sealed class TileOutput : IDisposable - { - /// - /// Initializes a new instance of the class. - /// - public TileOutput(int top, int height, IMemoryOwner coverageOwner, IMemoryOwner dirtyRowsOwner) - { - this.Top = top; - this.Height = height; - this.CoverageOwner = coverageOwner; - this.DirtyRowsOwner = dirtyRowsOwner; - } - - /// - /// Gets the tile top row relative to interest origin. - /// - public int Top { get; } - - /// - /// Gets the number of rows in this tile. - /// - public int Height { get; } - - /// - /// Gets the tile coverage buffer owner. - /// - public IMemoryOwner CoverageOwner { get; private set; } - - /// - /// Gets the tile dirty-row buffer owner. - /// - public IMemoryOwner DirtyRowsOwner { get; private set; } - - /// - /// Releases tile output buffers back to the allocator. - /// - public void Dispose() - { - this.CoverageOwner?.Dispose(); - this.DirtyRowsOwner?.Dispose(); - this.CoverageOwner = null!; - this.DirtyRowsOwner = null!; - } - } - - /// - /// Reusable per-worker scratch buffers used by tiled and sequential band rasterization. - /// - private sealed class WorkerScratch : IDisposable - { - private readonly int wordsPerRow; - private readonly int coverStride; - private readonly int width; - private readonly int tileCapacity; - private readonly IMemoryOwner bitVectorsOwner; - private readonly IMemoryOwner coverAreaOwner; - private readonly IMemoryOwner startCoverOwner; - private readonly IMemoryOwner rowHasBitsOwner; - private readonly IMemoryOwner rowTouchedOwner; - private readonly IMemoryOwner touchedRowsOwner; - private readonly IMemoryOwner scanlineOwner; - - private WorkerScratch( - int wordsPerRow, - int coverStride, - int width, - int tileCapacity, - IMemoryOwner bitVectorsOwner, - IMemoryOwner coverAreaOwner, - IMemoryOwner startCoverOwner, - IMemoryOwner rowHasBitsOwner, - IMemoryOwner rowTouchedOwner, - IMemoryOwner touchedRowsOwner, - IMemoryOwner scanlineOwner) - { - this.wordsPerRow = wordsPerRow; - this.coverStride = coverStride; - this.width = width; - this.tileCapacity = tileCapacity; - this.bitVectorsOwner = bitVectorsOwner; - this.coverAreaOwner = coverAreaOwner; - this.startCoverOwner = startCoverOwner; - this.rowHasBitsOwner = rowHasBitsOwner; - this.rowTouchedOwner = rowTouchedOwner; - this.touchedRowsOwner = touchedRowsOwner; - this.scanlineOwner = scanlineOwner; - } - - /// - /// Gets reusable scanline scratch for this worker. - /// - public Span Scanline => this.scanlineOwner.Memory.Span; - - /// - /// Allocates worker-local scratch sized for the configured tile/band capacity. - /// - public static WorkerScratch Create(MemoryAllocator allocator, int wordsPerRow, int coverStride, int width, int tileCapacity) - { - int bitVectorCapacity = checked(wordsPerRow * tileCapacity); - int coverAreaCapacity = checked(coverStride * tileCapacity); - IMemoryOwner bitVectorsOwner = allocator.Allocate(bitVectorCapacity, AllocationOptions.Clean); - IMemoryOwner coverAreaOwner = allocator.Allocate(coverAreaCapacity); - IMemoryOwner startCoverOwner = allocator.Allocate(tileCapacity, AllocationOptions.Clean); - IMemoryOwner rowHasBitsOwner = allocator.Allocate(tileCapacity, AllocationOptions.Clean); - IMemoryOwner rowTouchedOwner = allocator.Allocate(tileCapacity, AllocationOptions.Clean); - IMemoryOwner touchedRowsOwner = allocator.Allocate(tileCapacity); - IMemoryOwner scanlineOwner = allocator.Allocate(width); - - return new WorkerScratch( - wordsPerRow, - coverStride, - width, - tileCapacity, - bitVectorsOwner, - coverAreaOwner, - startCoverOwner, - rowHasBitsOwner, - rowTouchedOwner, - touchedRowsOwner, - scanlineOwner); - } - - /// - /// Creates a context view over this scratch for the requested band height. - /// - public Context CreateContext(int bandHeight, IntersectionRule intersectionRule, RasterizationMode rasterizationMode) - { - if ((uint)bandHeight > (uint)this.tileCapacity) - { - ThrowBandHeightExceedsScratchCapacity(); - } - - int bitVectorCount = checked(this.wordsPerRow * bandHeight); - int coverAreaCount = checked(this.coverStride * bandHeight); - return new Context( - this.bitVectorsOwner.Memory.Span[..bitVectorCount], - this.coverAreaOwner.Memory.Span[..coverAreaCount], - this.startCoverOwner.Memory.Span[..bandHeight], - this.rowHasBitsOwner.Memory.Span[..bandHeight], - this.rowTouchedOwner.Memory.Span[..bandHeight], - this.touchedRowsOwner.Memory.Span[..bandHeight], - this.width, - bandHeight, - this.wordsPerRow, - this.coverStride, - intersectionRule, - rasterizationMode); - } - - /// - /// Releases worker-local scratch buffers back to the allocator. - /// - public void Dispose() - { - this.bitVectorsOwner.Dispose(); - this.coverAreaOwner.Dispose(); - this.startCoverOwner.Dispose(); - this.rowHasBitsOwner.Dispose(); - this.rowTouchedOwner.Dispose(); - this.touchedRowsOwner.Dispose(); - this.scanlineOwner.Dispose(); - } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanning.MD b/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanning.MD deleted file mode 100644 index e4fb24455..000000000 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanning.MD +++ /dev/null @@ -1,224 +0,0 @@ -# Polygon Scanner (Fixed-Point, Tiled + Banded Fallback) - -This document describes the current `PolygonScanner` implementation in -`src/ImageSharp.Drawing/Shapes/Rasterization/PolygonScanner.cs`. - -The scanner is a fixed-point, area/cover rasterizer inspired by Blaze-style -scan conversion. - -https://github.com/aurimasg/blaze (MIT-Licensed) - -## Goals - -- Robustly rasterize arbitrary tessellated polygon rings (including self intersections). -- Support `EvenOdd` and `NonZero` fill rules. -- Keep temporary memory bounded for large targets. -- Emit coverage spans efficiently for blending. - -## High-Level Pipeline - -``` -IPath - | - v -TessellatedMultipolygon.Create(...) - | - v -Choose execution mode: - | - +--> Parallel row-tiles (default rasterizer path) - | | - | +--> Build edge table once (global local-space edges) - | +--> Assign edges to tile rows - | +--> Rasterize each tile in parallel using worker-local scratch - | +--> Emit tile outputs in deterministic top-to-bottom order - | - +--> Sequential band loop (scanline baseline + fallback) - | - +--> Build edge table once (shared with parallel path) - +--> Assign edges to sequential bands - +--> Reuse worker scratch across bands - +--> Rasterize band-local edge subsets into cover/area accumulators - +--> Convert accumulators to coverage scanlines - +--> Invoke rasterizer callback per dirty row -``` - -## Coordinate System and Precision - -- Geometry is transformed to scanner-local coordinates: - - `xLocal = (x - interest.Left) + samplingOffsetX` - - `yLocal = y - interest.Top` (global local-space edge table) - - Per tile/band pass uses `yLocal - currentBandTop` -- Scanner math uses signed 24.8 fixed point: - - `FixedShift = 8` - - `FixedOne = 256` -- Coverage is normalized to `[0..1]` with 256 steps: - - `CoverageStepCount = 256` - - `CoverageScale = 1 / 256` - -This means 1 fixed unit in Y equals 1/256 pixel row resolution. - -## Memory Model and Banded Scratch - -The scanner bounds scratch memory with a per-band budget: - -- `BandMemoryBudgetBytes = 64 MB` -- Rows per band are computed from per-row byte cost. - -Per-row temporary storage: - -``` -bitVectors: wordsPerRow * sizeof(nuint) -coverArea : (width * 2) * sizeof(int) -startCover: 1 * sizeof(int) -``` - -Scratch buffers are reused per band/tile worker: - -``` -bitVectors : [bandHeight][wordsPerRow] // bitset marks touched columns -coverArea : [bandHeight][width * 2] // per x: [deltaCover, deltaArea] -startCover : [bandHeight] // carry-in cover at x=0 -rowHasBits : [bandHeight] // fast "row touched" flag -scanline : [width] float // output coverage row -``` - -If width/height are too large for safe indexing math, rasterization throws -`ImageProcessingException`. - -Parallel mode additionally buffers per-tile output coverage before ordered emit. -This path is capped by `ParallelOutputPixelBudget` to avoid pathological output -buffer growth. - -## Edge Rasterization Stage - -For each tessellated ring edge `(p0 -> p1)` during edge-table build: - -1. Translate to local coordinates. -2. Reject non-finite coordinates. -3. Clip vertically to scanner bounds. -4. Record edge row range for tile assignment. - -During tile/band rasterization: - -1. Clip edge to current tile/band vertical bounds. -2. Convert endpoints to 24.8 fixed. -3. Skip horizontal edges (`fy0 == fy1`). -4. Route to directional line walkers (`LineDownR`, `LineUpL`, etc.). - -The walkers decompose edges into affected cells and call: - -- `Cell(...)` for general segments -- `CellVertical(...)` for vertical segments - -Both end up in `AddCell(row, column, deltaCover, deltaArea)`. - -`AddCell` updates: - -- `coverArea[row, column * 2 + 0] += deltaCover` -- `coverArea[row, column * 2 + 1] += deltaArea` -- bit in `bitVectors[row]` for `column` -- `rowHasBits[row] = 1` - -If `column < 0`, the contribution is folded into `startCover[row]` so coverage -to the left of the interest rectangle still influences pixels at `x >= 0`. - -## Scanline Emission Stage - -For each row in the current band: - -1. Skip quickly if `startCover[row] == 0` and `rowHasBits[row] == 0`. -2. Iterate set bits in the row bitset (`TrailingZeroCount` walk). -3. Reconstruct area/cover state at each touched `x`. -4. Convert signed accumulated area to coverage via fill rule. -5. Coalesce equal coverage into spans. -6. Fill `scanline[start..end]` for each non-zero span. -7. Invoke callback for dirty rows only. - -Core conversion: - -``` -area = coverArea[deltaArea] + (cover << 9) -``` - -`cover` is updated incrementally by `deltaCover`. - -## Fill Rule Handling - -### NonZero - -``` -absArea = abs(signedArea) -coverage = min(absArea, 256) / 256 -``` - -### EvenOdd - -``` -wrapped = absArea & 511 -if wrapped > 256: wrapped = 512 - wrapped -coverage = min(wrapped, 256) / 256 -``` - -This is done in `AreaToCoverage(int area)`. - -## Why This Handles Self Intersections - -The scanner does not require geometric boolean normalization first. -Overlaps are resolved by accumulated area/cover integration and final fill-rule -mapping (`EvenOdd` or `NonZero`), so winding/parity behavior is decided at -rasterization time. - -## Fast Paths and Practical Optimizations - -- One tessellation build per rasterization call. -- Parallel path builds a single edge table and reuses it across tiles. -- Worker-local scratch reuse avoids per-tile scratch allocations. -- Sequential path reuses band buffers across the full Y range. -- `rowHasBits` avoids scanning all words in empty rows. -- Bitset iteration visits only touched columns. -- Span coalescing reduces per-pixel operations before blending. - -## Notes on Public Options - -- `RasterizerOptions.RasterizationMode` controls whether scanner output is: - - `Antialiased`: continuous coverage in `[0, 1]` - - `Aliased`: binary coverage (`0` or `1`), thresholded in the scanner -- `RasterizerSamplingOrigin` still affects X alignment (`PixelBoundary` vs `PixelCenter`). - -## Data Flow Diagram (Row-Level) - -``` - per-edge writes - | - v - +----------------------+ - | coverArea[row][x,*] | deltaCover + deltaArea - +----------------------+ - | - +--> bitVectors[row] set bit x - | - +--> rowHasBits[row] = 1 - | - +--> startCover[row] (for x < 0 contributions) - -Then during emit: - -bitVectors[row] -> touched x list -> accumulate cover/area -> coverage spans - | - v - scanline[width] - | - v - Rasterizer callback -``` - -## Failure Modes and Diagnostics - -- Exception: interest too large for bounded scratch/output buffers or indexing. -- Symptoms like missing fill are usually from invalid input geometry (NaN/Inf) or - ring construction upstream; scanner explicitly skips non-finite edges. -- Performance hotspots are typically in: - - edge walking (`RasterizeLine` family), - - fill-rule conversion (`EmitRowCoverage`), - - downstream blending/compositing callbacks. diff --git a/src/ImageSharp.Drawing/Shapes/Rasterization/ScanlineRasterizer.cs b/src/ImageSharp.Drawing/Shapes/Rasterization/ScanlineRasterizer.cs deleted file mode 100644 index 6a2183c0b..000000000 --- a/src/ImageSharp.Drawing/Shapes/Rasterization/ScanlineRasterizer.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Shapes.Rasterization; - -/// -/// Single-pass CPU scanline rasterizer. -/// -/// -/// This implementation directly rasterizes the whole interest rectangle in one pass. -/// It is retained as a compact fallback/reference implementation and as an explicit -/// non-tiled option for profiling and comparison. -/// -internal sealed class ScanlineRasterizer : IRasterizer -{ - /// - /// Gets the singleton scanline rasterizer instance. - /// - public static ScanlineRasterizer Instance { get; } = new(); - - /// - public void Rasterize( - IPath path, - in RasterizerOptions options, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - { - Guard.NotNull(path, nameof(path)); - Guard.NotNull(allocator, nameof(allocator)); - Guard.NotNull(scanlineHandler, nameof(scanlineHandler)); - - Rectangle interest = options.Interest; - if (interest.Equals(Rectangle.Empty)) - { - return; - } - - PolygonScanner.RasterizeSequential(path, options, allocator, ref state, scanlineHandler); - } -} diff --git a/src/ImageSharp.Drawing/Shapes/TessellatedMultipolygon.cs b/src/ImageSharp.Drawing/Shapes/TessellatedMultipolygon.cs deleted file mode 100644 index eade34439..000000000 --- a/src/ImageSharp.Drawing/Shapes/TessellatedMultipolygon.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Collections; -using SixLabors.ImageSharp.Drawing.Shapes.Helpers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Shapes; - -/// -/// Compact representation of a multipolygon. -/// Applies some rules which are optimal to implement geometric algorithms: -/// - Outer contour is oriented "Positive" (CCW in world coords, CW on screen) -/// - Holes are oriented "Negative" (CW in world, CCW on screen) -/// - First vertex is always repeated at the end of the span in each ring -/// -internal sealed class TessellatedMultipolygon : IDisposable, IReadOnlyList -{ - private readonly Ring[] rings; - - private TessellatedMultipolygon(Ring[] rings) - { - this.rings = rings; - this.TotalVertexCount = rings.Sum(r => r.VertexCount); - } - - public int TotalVertexCount { get; } - - public int Count => this.rings.Length; - - public Ring this[int index] => this.rings[index]; - - public static TessellatedMultipolygon Create(IPath path, MemoryAllocator memoryAllocator) - { - if (path is IInternalPathOwner ipo) - { - IReadOnlyList internalPaths = ipo.GetRingsAsInternalPath(); - - // If we have only one ring, we can change it's orientation without negative side-effects. - // Since the algorithm works best with positively-oriented polygons, - // we enforce the orientation for best output quality. - bool enforcePositiveOrientationOnFirstRing = internalPaths.Count == 1; - - Ring[] rings = new Ring[internalPaths.Count]; - IMemoryOwner pointBuffer = internalPaths[0].ExtractVertices(memoryAllocator); - RepeatFirstVertexAndEnsureOrientation(pointBuffer.Memory.Span, enforcePositiveOrientationOnFirstRing); - rings[0] = new Ring(pointBuffer); - - for (int i = 1; i < internalPaths.Count; i++) - { - pointBuffer = internalPaths[i].ExtractVertices(memoryAllocator); - RepeatFirstVertexAndEnsureOrientation(pointBuffer.Memory.Span, false); - rings[i] = new Ring(pointBuffer); - } - - return new TessellatedMultipolygon(rings); - } - else - { - ReadOnlyMemory[] points = [.. path.Flatten().Select(sp => sp.Points)]; - - // If we have only one ring, we can change it's orientation without negative side-effects. - // Since the algorithm works best with positively-oriented polygons, - // we enforce the orientation for best output quality. - bool enforcePositiveOrientationOnFirstRing = points.Length == 1; - - Ring[] rings = new Ring[points.Length]; - rings[0] = MakeRing(points[0], enforcePositiveOrientationOnFirstRing, memoryAllocator); - for (int i = 1; i < points.Length; i++) - { - rings[i] = MakeRing(points[i], false, memoryAllocator); - } - - return new TessellatedMultipolygon(rings); - } - - static Ring MakeRing(ReadOnlyMemory points, bool enforcePositiveOrientation, MemoryAllocator allocator) - { - IMemoryOwner buffer = allocator.Allocate(points.Length + 1); - Span span = buffer.Memory.Span; - points.Span.CopyTo(span); - RepeatFirstVertexAndEnsureOrientation(span, enforcePositiveOrientation); - return new Ring(buffer); - } - - static void RepeatFirstVertexAndEnsureOrientation(Span span, bool enforcePositiveOrientation) - { - // Repeat first vertex for perf: - span[^1] = span[0]; - - if (enforcePositiveOrientation) - { - TopologyUtilities.EnsureOrientation(span, 1); - } - } - } - - public void Dispose() - { - foreach (Ring ring in this.rings) - { - ring.Dispose(); - } - } - - public IEnumerator GetEnumerator() => this.rings.AsEnumerable().GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - - internal sealed class Ring : IDisposable - { - private readonly IMemoryOwner buffer; - private Memory memory; - - internal Ring(IMemoryOwner buffer) - { - this.buffer = buffer; - this.memory = buffer.Memory; - } - - public ReadOnlySpan Vertices => this.memory.Span; - - public int VertexCount => this.memory.Length - 1; // Last vertex is repeated - - public void Dispose() - { - this.buffer.Dispose(); - this.memory = default; - } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Text/BaseGlyphBuilder.cs b/src/ImageSharp.Drawing/Shapes/Text/BaseGlyphBuilder.cs deleted file mode 100644 index 2966e3f7a..000000000 --- a/src/ImageSharp.Drawing/Shapes/Text/BaseGlyphBuilder.cs +++ /dev/null @@ -1,457 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.Fonts; -using SixLabors.Fonts.Rendering; -using SixLabors.ImageSharp.Drawing.Processing; - -namespace SixLabors.ImageSharp.Drawing.Text; - -/// -/// Defines a base rendering surface that Fonts can use to generate shapes. -/// -internal class BaseGlyphBuilder : IGlyphRenderer -{ - private Vector2 currentPoint; - private GlyphRendererParameters parameters; - - // Tracks whether geometry was emitted inside BeginLayer/EndLayer pairs for this glyph. - private bool usedLayers; - - // Tracks whether we are currently inside a layer block. - private bool inLayer; - - // Per-GRAPHEME layered capture (aggregate multiple glyphs of the same grapheme, e.g. COLR v0 layers): - private GlyphPathCollection.Builder? graphemeBuilder; - private int graphemePathCount; - private int currentGraphemeIndex = -1; - private readonly List currentGlyphs = []; - private TextDecorationDetails? previousUnderlineTextDecoration; - private TextDecorationDetails? previousOverlineTextDecoration; - private TextDecorationDetails? previousStrikeoutTextDecoration; - - // Per-layer (within current grapheme) bookkeeping: - private int layerStartIndex; - private Paint? currentLayerPaint; - private FillRule currentLayerFillRule; - private ClipQuad? currentClipBounds; - - public BaseGlyphBuilder() => this.Builder = new PathBuilder(); - - public BaseGlyphBuilder(Matrix3x2 transform) => this.Builder = new PathBuilder(transform); - - /// - /// Gets the flattened paths captured for all glyphs/graphemes. - /// - public IPathCollection Paths => new PathCollection(this.CurrentPaths); - - /// - /// Gets the layer-preserving collections captured per grapheme in rendering order. - /// Each entry aggregates all glyph layers that belong to a single grapheme cluster. - /// - public IReadOnlyList Glyphs => this.currentGlyphs; - - protected PathBuilder Builder { get; } - - /// - /// Gets the paths captured for the current glyph/grapheme. - /// - protected List CurrentPaths { get; } = []; - - void IGlyphRenderer.EndText() - { - // Finalize the last grapheme, if any: - if (this.graphemeBuilder is not null && this.graphemePathCount > 0) - { - this.currentGlyphs.Add(this.graphemeBuilder.Build()); - } - - this.graphemeBuilder = null; - this.graphemePathCount = 0; - this.currentGraphemeIndex = -1; - this.previousUnderlineTextDecoration = null; - this.previousOverlineTextDecoration = null; - this.previousStrikeoutTextDecoration = null; - - this.EndText(); - } - - void IGlyphRenderer.BeginText(in FontRectangle bounds) => this.BeginText(bounds); - - bool IGlyphRenderer.BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) - { - // If grapheme changed, flush previous aggregate and start a new one: - if (this.graphemeBuilder is not null && this.currentGraphemeIndex != parameters.GraphemeIndex) - { - if (this.graphemePathCount > 0) - { - this.currentGlyphs.Add(this.graphemeBuilder.Build()); - } - - this.graphemeBuilder = null; - this.graphemePathCount = 0; - } - - if (this.graphemeBuilder is null) - { - this.graphemeBuilder = new GlyphPathCollection.Builder(); - this.currentGraphemeIndex = parameters.GraphemeIndex; - this.graphemePathCount = 0; - } - - this.parameters = parameters; - this.Builder.Clear(); - this.usedLayers = false; - this.inLayer = false; - - this.layerStartIndex = this.graphemePathCount; - this.currentLayerPaint = null; - this.currentLayerFillRule = FillRule.NonZero; - this.currentClipBounds = null; - this.BeginGlyph(in bounds, in parameters); - return true; - } - - /// - void IGlyphRenderer.BeginFigure() => this.Builder.StartFigure(); - - /// - void IGlyphRenderer.CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point) - { - this.Builder.AddCubicBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); - this.currentPoint = point; - } - - /// - void IGlyphRenderer.EndGlyph() - { - // If the glyph did not open any explicit layer, treat its geometry as a single layer in the current grapheme: - if (!this.usedLayers) - { - IPath path = this.Builder.Build(); - - this.CurrentPaths.Add(path); - - if (this.graphemeBuilder is not null) - { - this.graphemeBuilder.AddPath(path); - this.graphemeBuilder.AddLayer( - startIndex: this.graphemePathCount, - count: 1, - paint: null, - fillRule: FillRule.NonZero, - bounds: path.Bounds, - kind: GlyphLayerKind.Glyph); - - this.graphemePathCount++; - } - } - - this.EndGlyph(); - this.Builder.Clear(); - this.inLayer = false; - this.usedLayers = false; - this.layerStartIndex = this.graphemePathCount; - } - - /// - void IGlyphRenderer.EndFigure() => this.Builder.CloseFigure(); - - /// - void IGlyphRenderer.LineTo(Vector2 point) - { - this.Builder.AddLine(this.currentPoint, point); - this.currentPoint = point; - } - - /// - void IGlyphRenderer.MoveTo(Vector2 point) - { - this.Builder.StartFigure(); - this.currentPoint = point; - } - - /// - void IGlyphRenderer.ArcTo(float radiusX, float radiusY, float rotation, bool largeArc, bool sweep, Vector2 point) - { - this.Builder.AddArc(this.currentPoint, radiusX, radiusY, rotation, largeArc, sweep, point); - this.currentPoint = point; - } - - /// - void IGlyphRenderer.QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point) - { - this.Builder.AddQuadraticBezier(this.currentPoint, secondControlPoint, point); - this.currentPoint = point; - } - - /// - void IGlyphRenderer.BeginLayer(Paint? paint, FillRule fillRule, ClipQuad? clipBounds) - { - this.usedLayers = true; - this.inLayer = true; - this.layerStartIndex = this.graphemePathCount; - this.currentLayerPaint = paint; - this.currentLayerFillRule = fillRule; - this.currentClipBounds = clipBounds; - - this.Builder.Clear(); - this.BeginLayer(paint, fillRule, clipBounds); - } - - /// - void IGlyphRenderer.EndLayer() - { - if (!this.inLayer) - { - return; - } - - IPath path = this.Builder.Build(); - - if (this.currentClipBounds is not null) - { - ClipQuad clip = this.currentClipBounds.Value; - PointF[] points = [clip.TopLeft, clip.TopRight, clip.BottomRight, clip.BottomLeft]; - LinearLineSegment segment = new(points); - Polygon polygon = new(segment); - - ShapeOptions options = new() - { - BooleanOperation = BooleanOperation.Intersection, - IntersectionRule = TextUtilities.MapFillRule(this.currentLayerFillRule) - }; - - path = path.Clip(options, polygon); - } - - this.CurrentPaths.Add(path); - - if (this.graphemeBuilder is not null) - { - this.graphemeBuilder.AddPath(path); - this.graphemeBuilder.AddLayer( - startIndex: this.layerStartIndex, - count: 1, - paint: this.currentLayerPaint, - fillRule: this.currentLayerFillRule, - bounds: path.Bounds, - kind: GlyphLayerKind.Painted); - - this.graphemePathCount++; - } - - this.Builder.Clear(); - this.inLayer = false; - this.currentLayerPaint = null; - this.currentLayerFillRule = FillRule.NonZero; - this.currentClipBounds = null; - this.EndLayer(); - } - - /// - void IGlyphRenderer.SetDecoration(TextDecorations textDecorations, Vector2 start, Vector2 end, float thickness) - { - if (thickness == 0) - { - return; - } - - // Clamp the thickness to whole pixels. - thickness = MathF.Max(1F, (float)Math.Round(thickness)); - IGlyphRenderer renderer = this; - - bool rotated = this.parameters.LayoutMode is GlyphLayoutMode.Vertical or GlyphLayoutMode.VerticalRotated; - Vector2 pad = rotated ? new Vector2(thickness * .5F, 0) : new Vector2(0, thickness * .5F); - - start = ClampToPixel(start, (int)thickness, rotated); - end = ClampToPixel(end, (int)thickness, rotated); - - // Sometimes the start and end points do not align properly leaving pixel sized gaps - // so we need to adjust them. Use any previous decoration to try and continue the line. - TextDecorationDetails? previous = textDecorations switch - { - TextDecorations.Underline => this.previousUnderlineTextDecoration, - TextDecorations.Overline => this.previousOverlineTextDecoration, - TextDecorations.Strikeout => this.previousStrikeoutTextDecoration, - _ => null - }; - - if (previous != null) - { - float prevThickness = previous.Value.Thickness; - Vector2 prevStart = previous.Value.Start; - Vector2 prevEnd = previous.Value.End; - - // If the previous line is identical to the new one ignore it. - // This can happen when multiple glyph layers are used. - if (prevStart == start && prevEnd == end) - { - return; - } - - // Align the new line with the previous one if they are close enough. - // Use a 2 pixel threshold to account for anti-aliasing gaps. - if (rotated) - { - if (thickness == prevThickness - && prevEnd.Y + 2 >= start.Y - && prevEnd.X == start.X) - { - start = prevEnd; - } - } - else if (thickness == prevThickness - && prevEnd.Y == start.Y - && prevEnd.X + 2 >= start.X) - { - start = prevEnd; - } - } - - TextDecorationDetails current = new() - { - Start = start, - End = end, - Thickness = thickness - }; - - switch (textDecorations) - { - case TextDecorations.Underline: - this.previousUnderlineTextDecoration = current; - break; - case TextDecorations.Strikeout: - this.previousStrikeoutTextDecoration = current; - break; - case TextDecorations.Overline: - this.previousOverlineTextDecoration = current; - break; - } - - Vector2 a = start - pad; - Vector2 b = start + pad; - Vector2 c = end + pad; - Vector2 d = end - pad; - - // Drawing is always centered around the point so we need to offset by half. - Vector2 offset = Vector2.Zero; - if (textDecorations == TextDecorations.Overline) - { - // CSS overline is drawn above the position, so we need to move it up. - offset = rotated ? new Vector2(thickness * .5F, 0) : new Vector2(0, -(thickness * .5F)); - } - else if (textDecorations == TextDecorations.Underline) - { - // CSS underline is drawn below the position, so we need to move it down. - offset = rotated ? new Vector2(-(thickness * .5F), 0) : new Vector2(0, thickness * .5F); - } - - // We clamp the start and end points to the pixel grid to avoid anti-aliasing - // when there is no transform. - renderer.BeginFigure(); - renderer.MoveTo(ClampToPixel(a + offset)); - renderer.LineTo(ClampToPixel(b + offset)); - renderer.LineTo(ClampToPixel(c + offset)); - renderer.LineTo(ClampToPixel(d + offset)); - renderer.EndFigure(); - - IPath path = this.Builder.Build(); - - // If the path is degenerate (e.g. zero width line) we just skip it - // and return. This might happen when clamping moves the points. - if (path.Bounds.IsEmpty) - { - this.Builder.Clear(); - return; - } - - this.CurrentPaths.Add(path); - if (this.graphemeBuilder is not null) - { - this.graphemeBuilder.AddPath(path); - this.graphemeBuilder.AddLayer( - startIndex: this.layerStartIndex, - count: 1, - paint: this.currentLayerPaint, - fillRule: FillRule.NonZero, - bounds: path.Bounds, - kind: GlyphLayerKind.Decoration); - - this.graphemePathCount++; - } - - this.Builder.Clear(); - this.SetDecoration(textDecorations, start, end, thickness); - } - - /// - protected virtual void BeginText(in FontRectangle bounds) - { - } - - /// - protected virtual void BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) - { - } - - /// - protected virtual void EndGlyph() - { - } - - /// - protected virtual void EndText() - { - } - - /// - protected virtual void BeginLayer(Paint? paint, FillRule fillRule, ClipQuad? clipBounds) - { - } - - /// - protected virtual void EndLayer() - { - } - - public virtual TextDecorations EnabledDecorations() - => this.parameters.TextRun.TextDecorations; - - /// - public virtual void SetDecoration(TextDecorations textDecorations, Vector2 start, Vector2 end, float thickness) - { - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Point ClampToPixel(PointF point) => Point.Truncate(point); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static PointF ClampToPixel(PointF point, int thickness, bool rotated) - { - // Even. Clamp to whole pixels. - if ((thickness & 1) == 0) - { - return Point.Truncate(point); - } - - // Odd. Clamp to half pixels. - if (rotated) - { - return Point.Truncate(point) + new Vector2(.5F, 0); - } - - return Point.Truncate(point) + new Vector2(0, .5F); - } - - private struct TextDecorationDetails - { - public Vector2 Start { get; set; } - - public Vector2 End { get; set; } - - public float Thickness { get; internal set; } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Text/GlyphBuilder.cs b/src/ImageSharp.Drawing/Shapes/Text/GlyphBuilder.cs deleted file mode 100644 index 5f317f2ed..000000000 --- a/src/ImageSharp.Drawing/Shapes/Text/GlyphBuilder.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Drawing.Text; - -/// -/// rendering surface that Fonts can use to generate Shapes. -/// -internal class GlyphBuilder : BaseGlyphBuilder -{ - /// - /// Initializes a new instance of the class. - /// - public GlyphBuilder() - : this(Vector2.Zero) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The origin. - public GlyphBuilder(Vector2 origin) => this.Builder.SetOrigin(origin); -} diff --git a/src/ImageSharp.Drawing/Shapes/Text/PathGlyphBuilder.cs b/src/ImageSharp.Drawing/Shapes/Text/PathGlyphBuilder.cs deleted file mode 100644 index 706b772ac..000000000 --- a/src/ImageSharp.Drawing/Shapes/Text/PathGlyphBuilder.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.Fonts; -using SixLabors.Fonts.Rendering; - -namespace SixLabors.ImageSharp.Drawing.Text; - -/// -/// A rendering surface that Fonts can use to generate shapes by following a path. -/// -internal sealed class PathGlyphBuilder : GlyphBuilder -{ - private readonly IPathInternals path; - - /// - /// Initializes a new instance of the class. - /// - /// The path to render the glyphs along. - public PathGlyphBuilder(IPath path) - { - if (path is IPathInternals internals) - { - this.path = internals; - } - else - { - this.path = new ComplexPolygon(path); - } - } - - /// - protected override void BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) - => this.TransformGlyph(in bounds); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void TransformGlyph(in FontRectangle bounds) - { - // Find the point of this intersection along the given path. - // We want to find the point on the path that is closest to the center-bottom side of the glyph. - Vector2 half = new(bounds.Width * .5F, 0); - SegmentInfo pathPoint = this.path.PointAlongPath(bounds.Left + half.X); - - // Now offset to our target point since we're aligning the top-left location of our glyph against the path. - Vector2 translation = (Vector2)pathPoint.Point - bounds.Location - half + new Vector2(0, bounds.Top); - Matrix3x2 matrix = Matrix3x2.CreateTranslation(translation) * Matrix3x2.CreateRotation(pathPoint.Angle - MathF.PI, (Vector2)pathPoint.Point); - - this.Builder.SetTransform(matrix); - } -} diff --git a/src/ImageSharp.Drawing/SplitPathExtensions.cs b/src/ImageSharp.Drawing/SplitPathExtensions.cs new file mode 100644 index 000000000..b551121dc --- /dev/null +++ b/src/ImageSharp.Drawing/SplitPathExtensions.cs @@ -0,0 +1,201 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Drawing; + +/// +/// Extensions to for splitting paths into dash segments +/// without performing stroke expansion. +/// +public static class SplitPathExtensions +{ + // Safety limit: if the estimated number of dash segments exceeds this threshold, + // return the original path unsplit to avoid runaway segmentation from very short + // patterns applied to very long paths. + private const int MaxPatternSegments = 10000; + + /// + /// Splits the given path into dash segments based on the provided pattern. + /// Returns a composite path containing only the "on" segments as open sub-paths. + /// + /// The centerline path to split. + /// The stroke width (pattern elements are multiples of this). + /// The dash pattern. Each element is a multiple of . + /// A path containing the "on" dash segments. + public static IPath GenerateDashes(this IPath path, float strokeWidth, ReadOnlySpan pattern) + => path.GenerateDashes(strokeWidth, pattern, startOff: false); + + /// + /// Splits the given path into dash segments based on the provided pattern. + /// Returns a composite path containing only the "on" segments as open sub-paths. + /// + /// The centerline path to split. + /// The stroke width (pattern elements are multiples of this). + /// The dash pattern. Each element is a multiple of . + /// Whether the first item in the pattern is off rather than on. + /// A path containing the "on" dash segments. + public static IPath GenerateDashes(this IPath path, float strokeWidth, ReadOnlySpan pattern, bool startOff) + { + if (pattern.Length < 2) + { + return path; + } + + const float eps = 1e-6f; + + // Compute the absolute pattern length in path units to detect degenerate patterns. + float patternLength = 0f; + for (int i = 0; i < pattern.Length; i++) + { + patternLength += MathF.Abs(pattern[i]) * strokeWidth; + } + + // Fallback to the original path when the dash pattern is too small to be meaningful. + if (patternLength <= eps) + { + return path; + } + + IEnumerable simplePaths = path.Flatten(); + List segments = []; + List buffer = new(64); + + foreach (ISimplePath p in simplePaths) + { + bool online = !startOff; + int patternPos = 0; + float targetLength = pattern[patternPos] * strokeWidth; + + ReadOnlySpan pts = p.Points.Span; + if (pts.Length < 2) + { + continue; + } + + // Number of edges to traverse (closed paths wrap; open paths stop one short). + int edgeCount = p.IsClosed ? pts.Length : pts.Length - 1; + + // Compute total path length to estimate the number of dash segments. + // This avoids runaway segmentation when a very short pattern is applied + // to a very long path. + float totalLength = 0f; + for (int j = 0; j < edgeCount; j++) + { + int nextIndex = p.IsClosed ? (j + 1) % pts.Length : j + 1; + totalLength += Vector2.Distance(pts[j], pts[nextIndex]); + } + + if (totalLength > eps) + { + float estimatedSegments = (totalLength / patternLength) * pattern.Length; + if (estimatedSegments > MaxPatternSegments) + { + return path; + } + } + + int ei = 0; + Vector2 current = pts[0]; + + while (ei < edgeCount) + { + int nextIndex = p.IsClosed ? (ei + 1) % pts.Length : ei + 1; + Vector2 next = pts[nextIndex]; + float segLen = Vector2.Distance(current, next); + + // Skip degenerate zero-length segments. + if (segLen <= eps) + { + current = next; + ei++; + continue; + } + + // Accumulate into the current dash span when the segment is shorter + // than the remaining target length. + if (segLen + eps < targetLength) + { + if (online) + { + buffer.Add(current); + } + + current = next; + ei++; + targetLength -= segLen; + continue; + } + + // Close out a dash span when the segment length matches the target. + if (MathF.Abs(segLen - targetLength) <= eps) + { + if (online) + { + buffer.Add(current); + buffer.Add(next); + FlushBuffer(buffer, segments); + } + + buffer.Clear(); + online = !online; + current = next; + ei++; + patternPos = (patternPos + 1) % pattern.Length; + targetLength = pattern[patternPos] * strokeWidth; + continue; + } + + // Split inside this segment to end the current dash span. + float t = targetLength / segLen; + Vector2 split = current + (t * (next - current)); + + if (online) + { + buffer.Add(current); + buffer.Add(split); + FlushBuffer(buffer, segments); + } + + buffer.Clear(); + online = !online; + current = split; // continue along the same geometric segment + patternPos = (patternPos + 1) % pattern.Length; + targetLength = pattern[patternPos] * strokeWidth; + } + + // Flush the tail of the last dash span, if any. + if (buffer.Count > 0) + { + if (online) + { + buffer.Add(current); + FlushBuffer(buffer, segments); + } + + buffer.Clear(); + } + } + + if (segments.Count == 0) + { + return path; + } + + if (segments.Count == 1) + { + return segments[0]; + } + + return new ComplexPolygon(segments); + } + + private static void FlushBuffer(List buffer, List segments) + { + if (buffer.Count >= 2 && buffer[0] != buffer[^1]) + { + segments.Add(new Path(new LinearLineSegment([.. buffer]))); + } + } +} diff --git a/src/ImageSharp.Drawing/Shapes/Star.cs b/src/ImageSharp.Drawing/Star.cs similarity index 97% rename from src/ImageSharp.Drawing/Shapes/Star.cs rename to src/ImageSharp.Drawing/Star.cs index ba5a261ca..241b1bd44 100644 --- a/src/ImageSharp.Drawing/Shapes/Star.cs +++ b/src/ImageSharp.Drawing/Star.cs @@ -87,7 +87,7 @@ private static LinearLineSegment CreateSegment(Vector2 location, float innerRadi distance = distanceVectorInner; } - Vector2 rotated = Vector2.Transform(distance, Matrix3x2.CreateRotation(current)); + Vector2 rotated = PointF.Transform(distance, Matrix4x4.CreateRotationZ(current)); points[i] = rotated + location; diff --git a/src/ImageSharp.Drawing/Text/BaseGlyphBuilder.cs b/src/ImageSharp.Drawing/Text/BaseGlyphBuilder.cs new file mode 100644 index 000000000..0870a5744 --- /dev/null +++ b/src/ImageSharp.Drawing/Text/BaseGlyphBuilder.cs @@ -0,0 +1,582 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.Fonts; +using SixLabors.Fonts.Rendering; +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Text; + +/// +/// Defines a base rendering surface that Fonts can use to generate shapes. +/// +internal class BaseGlyphBuilder : IGlyphRenderer +{ + /// + /// The last point emitted by MoveTo / LineTo / curve commands. + /// Used as the implicit start of the next segment. + /// + private Vector2 currentPoint; + + /// + /// Snapshot of the for the glyph currently + /// being processed. Set at the start of each BeginGlyph call and read by + /// SetDecoration to determine layout orientation. + /// + private GlyphRendererParameters parameters; + + // Tracks whether geometry was emitted inside BeginLayer/EndLayer pairs for this glyph. + // When true, EndGlyph skips its default single-layer path capture because layers + // already contributed their paths individually. + private bool usedLayers; + + // Tracks whether we are currently inside a layer block. + // Guards against unbalanced EndLayer calls. + private bool inLayer; + + // --- Per-GRAPHEME layered capture --- + // A grapheme cluster (e.g. a base glyph + COLR v0 color layers) may span + // multiple BeginGlyph/EndGlyph calls. These fields aggregate all layers + // belonging to the same grapheme into a single GlyphPathCollection. + private GlyphPathCollection.Builder? graphemeBuilder; + private int graphemePathCount; + private int currentGraphemeIndex = -1; + private readonly List currentGlyphs = []; + + // Previous decoration details per decoration type, used to stitch adjacent + // decorations together and eliminate sub-pixel gaps between glyphs. + private TextDecorationDetails? previousUnderlineTextDecoration; + private TextDecorationDetails? previousOverlineTextDecoration; + private TextDecorationDetails? previousStrikeoutTextDecoration; + + // Per-layer (within current grapheme) bookkeeping: + private int layerStartIndex; + private Paint? currentLayerPaint; + private FillRule currentLayerFillRule; + private ClipQuad? currentClipBounds; + + /// + /// Initializes a new instance of the class + /// with an identity transform. + /// + public BaseGlyphBuilder() => this.Builder = new PathBuilder(); + + /// + /// Initializes a new instance of the class + /// with the specified transform applied to all incoming glyph geometry. + /// + /// A matrix transform applied to every point received from the font engine. + public BaseGlyphBuilder(Matrix4x4 transform) => this.Builder = new PathBuilder(transform); + + /// + /// Gets the flattened paths captured for all glyphs/graphemes. + /// + public IPathCollection Paths => new PathCollection(this.CurrentPaths); + + /// + /// Gets the layer-preserving collections captured per grapheme in rendering order. + /// Each entry aggregates all glyph layers that belong to a single grapheme cluster. + /// + public IReadOnlyList Glyphs => this.currentGlyphs; + + /// + /// Gets the used to accumulate outline segments + /// (MoveTo, LineTo, curves) for the current glyph or layer. + /// The builder is cleared between glyphs / layers. + /// + protected PathBuilder Builder { get; } + + /// + /// Gets the running list of all instances produced so far + /// (glyph outlines, layer outlines, and decoration rectangles). Subclasses + /// read from the end of this list (e.g. CurrentPaths[^1]) to obtain + /// the most recently built path. + /// + protected List CurrentPaths { get; } = []; + + /// + /// Called by the font engine after all glyphs in the text block have been rendered. + /// Flushes any in-progress grapheme aggregate and resets per-text-block state. + /// + void IGlyphRenderer.EndText() + { + // Finalize the last grapheme, if any: + if (this.graphemeBuilder is not null && this.graphemePathCount > 0) + { + this.currentGlyphs.Add(this.graphemeBuilder.Build()); + } + + this.graphemeBuilder = null; + this.graphemePathCount = 0; + this.currentGraphemeIndex = -1; + this.previousUnderlineTextDecoration = null; + this.previousOverlineTextDecoration = null; + this.previousStrikeoutTextDecoration = null; + + this.EndText(); + } + + void IGlyphRenderer.BeginText(in FontRectangle bounds) => this.BeginText(bounds); + + /// + /// Called by the font engine before emitting outline data for a single glyph. + /// Manages grapheme-cluster transitions and resets per-glyph state. + /// + /// + /// to have the font engine emit the full outline + /// (MoveTo/LineTo/curves/EndGlyph); to skip it entirely, + /// which is used by caching subclasses when the glyph path is already available. + /// + bool IGlyphRenderer.BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) + { + // If grapheme changed, flush previous aggregate and start a new one: + if (this.graphemeBuilder is not null && this.currentGraphemeIndex != parameters.GraphemeIndex) + { + if (this.graphemePathCount > 0) + { + this.currentGlyphs.Add(this.graphemeBuilder.Build()); + } + + this.graphemeBuilder = null; + this.graphemePathCount = 0; + } + + if (this.graphemeBuilder is null) + { + this.graphemeBuilder = new GlyphPathCollection.Builder(); + this.currentGraphemeIndex = parameters.GraphemeIndex; + this.graphemePathCount = 0; + } + + this.parameters = parameters; + this.Builder.Clear(); + this.usedLayers = false; + this.inLayer = false; + + this.layerStartIndex = this.graphemePathCount; + this.currentLayerPaint = null; + this.currentLayerFillRule = FillRule.NonZero; + this.currentClipBounds = null; + return this.BeginGlyph(in bounds, in parameters); + } + + /// + void IGlyphRenderer.BeginFigure() => this.Builder.StartFigure(); + + /// + void IGlyphRenderer.CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point) + { + this.Builder.AddCubicBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); + this.currentPoint = point; + } + + /// + /// Called by the font engine after the outline for a single glyph has been fully emitted. + /// Builds the accumulated path and registers it as a grapheme layer unless explicit + /// BeginLayer/EndLayer pairs already handled layer registration. + /// + void IGlyphRenderer.EndGlyph() + { + // If the glyph did not open any explicit layer, treat its geometry as a single + // implicit layer so that non-color glyphs still produce a GlyphPathCollection entry. + if (!this.usedLayers) + { + IPath path = this.Builder.Build(); + + this.CurrentPaths.Add(path); + + if (this.graphemeBuilder is not null) + { + this.graphemeBuilder.AddPath(path); + this.graphemeBuilder.AddLayer( + startIndex: this.graphemePathCount, + count: 1, + paint: null, + fillRule: FillRule.NonZero, + bounds: path.Bounds, + kind: GlyphLayerKind.Glyph); + + this.graphemePathCount++; + } + } + + this.EndGlyph(); + this.Builder.Clear(); + this.inLayer = false; + this.usedLayers = false; + this.layerStartIndex = this.graphemePathCount; + } + + /// + void IGlyphRenderer.EndFigure() => this.Builder.CloseFigure(); + + /// + void IGlyphRenderer.LineTo(Vector2 point) + { + this.Builder.AddLine(this.currentPoint, point); + this.currentPoint = point; + } + + /// + void IGlyphRenderer.MoveTo(Vector2 point) + { + this.Builder.StartFigure(); + this.currentPoint = point; + } + + /// + void IGlyphRenderer.ArcTo(float radiusX, float radiusY, float rotation, bool largeArc, bool sweep, Vector2 point) + { + this.Builder.AddArc(this.currentPoint, radiusX, radiusY, rotation, largeArc, sweep, point); + this.currentPoint = point; + } + + /// + void IGlyphRenderer.QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point) + { + this.Builder.AddQuadraticBezier(this.currentPoint, secondControlPoint, point); + this.currentPoint = point; + } + + /// + /// Called by the font engine to begin a color layer within a COLR v0/v1 glyph. + /// Each layer receives its own paint, fill rule, and optional clip bounds. + /// + void IGlyphRenderer.BeginLayer(Paint? paint, FillRule fillRule, ClipQuad? clipBounds) + { + this.usedLayers = true; + this.inLayer = true; + this.layerStartIndex = this.graphemePathCount; + this.currentLayerPaint = paint; + this.currentLayerFillRule = fillRule; + this.currentClipBounds = clipBounds; + + this.Builder.Clear(); + this.BeginLayer(paint, fillRule, clipBounds); + } + + /// + /// Called by the font engine to close a color layer opened by BeginLayer. + /// Builds the layer path, applies any clip quad, and registers the result + /// as a painted layer in the current grapheme aggregate. + /// + void IGlyphRenderer.EndLayer() + { + if (!this.inLayer) + { + return; + } + + IPath path = this.Builder.Build(); + + // If the layer defines a clip quad (e.g. from COLR v1), intersect the + // built path with the quad polygon to constrain rendering. + if (this.currentClipBounds is not null) + { + ClipQuad clip = this.currentClipBounds.Value; + PointF[] points = [clip.TopLeft, clip.TopRight, clip.BottomRight, clip.BottomLeft]; + LinearLineSegment segment = new(points); + Polygon polygon = new(segment); + + ShapeOptions options = new() + { + BooleanOperation = BooleanOperation.Intersection, + IntersectionRule = TextUtilities.MapFillRule(this.currentLayerFillRule) + }; + + path = path.Clip(options, polygon); + } + + this.CurrentPaths.Add(path); + + if (this.graphemeBuilder is not null) + { + this.graphemeBuilder.AddPath(path); + this.graphemeBuilder.AddLayer( + startIndex: this.layerStartIndex, + count: 1, + paint: this.currentLayerPaint, + fillRule: this.currentLayerFillRule, + bounds: path.Bounds, + kind: GlyphLayerKind.Painted); + + this.graphemePathCount++; + } + + this.Builder.Clear(); + this.inLayer = false; + this.currentLayerPaint = null; + this.currentLayerFillRule = FillRule.NonZero; + this.currentClipBounds = null; + this.EndLayer(); + } + + /// + /// Called by the font engine to emit a text decoration (underline, strikeout, or overline) + /// for the current glyph. Builds a filled rectangle path from the start/end positions and + /// thickness, then registers it as a layer. + /// Adjacent decorations are stitched together using the previous decoration details to + /// eliminate sub-pixel gaps caused by font metric rounding. + /// + void IGlyphRenderer.SetDecoration(TextDecorations textDecorations, Vector2 start, Vector2 end, float thickness) + { + if (thickness == 0) + { + return; + } + + // Clamp the thickness to whole pixels. + thickness = MathF.Max(1F, (float)Math.Round(thickness)); + IGlyphRenderer renderer = this; + + bool rotated = this.parameters.LayoutMode is GlyphLayoutMode.Vertical or GlyphLayoutMode.VerticalRotated; + Vector2 pad = rotated ? new Vector2(thickness * .5F, 0) : new Vector2(0, thickness * .5F); + + start = ClampToPixel(start, (int)thickness, rotated); + end = ClampToPixel(end, (int)thickness, rotated); + + // Sometimes the start and end points do not align properly leaving pixel sized gaps + // so we need to adjust them. Use any previous decoration to try and continue the line. + TextDecorationDetails? previous = textDecorations switch + { + TextDecorations.Underline => this.previousUnderlineTextDecoration, + TextDecorations.Overline => this.previousOverlineTextDecoration, + TextDecorations.Strikeout => this.previousStrikeoutTextDecoration, + _ => null + }; + + if (previous != null) + { + float prevThickness = previous.Value.Thickness; + Vector2 prevStart = previous.Value.Start; + Vector2 prevEnd = previous.Value.End; + + // If the previous line is identical to the new one ignore it. + // This can happen when multiple glyph layers are used. + if (prevStart == start && prevEnd == end) + { + return; + } + + // Align the new line with the previous one if they are close enough. + // Use a 2 pixel threshold to account for anti-aliasing gaps. + if (rotated) + { + if (thickness == prevThickness + && prevEnd.Y + 2 >= start.Y + && prevEnd.X == start.X) + { + start = prevEnd; + } + } + else if (thickness == prevThickness + && prevEnd.Y == start.Y + && prevEnd.X + 2 >= start.X) + { + start = prevEnd; + } + } + + TextDecorationDetails current = new() + { + Start = start, + End = end, + Thickness = thickness + }; + + switch (textDecorations) + { + case TextDecorations.Underline: + this.previousUnderlineTextDecoration = current; + break; + case TextDecorations.Strikeout: + this.previousStrikeoutTextDecoration = current; + break; + case TextDecorations.Overline: + this.previousOverlineTextDecoration = current; + break; + } + + Vector2 a = start - pad; + Vector2 b = start + pad; + Vector2 c = end + pad; + Vector2 d = end - pad; + + // Drawing is always centered around the point so we need to offset by half. + Vector2 offset = Vector2.Zero; + if (textDecorations == TextDecorations.Overline) + { + // CSS overline is drawn above the position, so we need to move it up. + offset = rotated ? new Vector2(thickness * .5F, 0) : new Vector2(0, -(thickness * .5F)); + } + else if (textDecorations == TextDecorations.Underline) + { + // CSS underline is drawn below the position, so we need to move it down. + offset = rotated ? new Vector2(-(thickness * .5F), 0) : new Vector2(0, thickness * .5F); + } + + // We clamp the start and end points to the pixel grid to avoid anti-aliasing + // when there is no transform. + renderer.BeginFigure(); + renderer.MoveTo(ClampToPixel(a + offset)); + renderer.LineTo(ClampToPixel(b + offset)); + renderer.LineTo(ClampToPixel(c + offset)); + renderer.LineTo(ClampToPixel(d + offset)); + renderer.EndFigure(); + + IPath path = this.Builder.Build(); + + // If the path is degenerate (e.g. zero width line) we just skip it + // and return. This might happen when clamping moves the points. + if (path.Bounds.IsEmpty) + { + this.Builder.Clear(); + return; + } + + this.CurrentPaths.Add(path); + if (this.graphemeBuilder is not null) + { + // Decorations are emitted as independent paths; each layer must point + // at the path index appended for this specific decoration. + this.graphemeBuilder.AddPath(path); + this.graphemeBuilder.AddLayer( + startIndex: this.graphemePathCount, + count: 1, + paint: this.currentLayerPaint, + fillRule: FillRule.NonZero, + bounds: path.Bounds, + kind: GlyphLayerKind.Decoration); + + this.graphemePathCount++; + } + + this.Builder.Clear(); + this.SetDecoration(textDecorations, start, end, thickness); + } + + /// + protected virtual void BeginText(in FontRectangle bounds) + { + } + + /// + /// Called after base-class bookkeeping in IGlyphRenderer.BeginGlyph. + /// Subclasses override this to apply transforms, consult caches, or opt out of + /// outline emission by returning . + /// + /// The font-metric bounding rectangle of the glyph. + /// Identifies the glyph (id, font, layout mode, text run, etc.). + /// + /// to receive outline data and an EndGlyph call; + /// to skip outline emission for this glyph entirely. + /// + protected virtual bool BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) + => true; + + /// + /// Called after the base class has built and registered the glyph path. + /// Subclasses override this to emit drawing operations from the captured path. + /// + protected virtual void EndGlyph() + { + } + + /// + /// Called after the base class has flushed all grapheme aggregates. + /// Subclasses override this for any per-text-block finalization. + /// + protected virtual void EndText() + { + } + + /// + /// Called when a COLR color layer begins. Subclasses override this to + /// capture the layer's paint and composite mode. + /// + /// The paint for this color layer, or for the default foreground. + /// The fill rule to use when rasterizing this layer. + /// Optional clip quad constraining the layer region. + protected virtual void BeginLayer(Paint? paint, FillRule fillRule, ClipQuad? clipBounds) + { + } + + /// + /// Called when a COLR color layer ends. Subclasses override this to + /// emit the layer as a drawing operation. + /// + protected virtual void EndLayer() + { + } + + /// + /// Returns the set of text decorations enabled for the current glyph. + /// The font engine calls this to decide which SetDecoration callbacks to emit. + /// Subclasses override this to include decorations implied by rich-text pens + /// (e.g. ). + /// + /// A flags enum of the active text decorations. + public virtual TextDecorations EnabledDecorations() + => this.parameters.TextRun.TextDecorations; + + /// + /// Override point for subclasses to emit decoration drawing operations. + /// Called after the base class has built and registered the decoration path + /// in . + /// + /// The type of decoration (underline, strikeout, or overline). + /// The start position of the decoration line. + /// The end position of the decoration line. + /// The thickness of the decoration line in pixels. + public virtual void SetDecoration(TextDecorations textDecorations, Vector2 start, Vector2 end, float thickness) + { + } + + /// + /// Truncates a floating-point position to the nearest whole pixel toward negative infinity. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Point ClampToPixel(PointF point) => Point.Truncate(point); + + /// + /// Snaps a decoration endpoint to the pixel grid, taking stroke thickness and + /// orientation into account. Even-thickness lines snap to whole pixels; odd-thickness + /// lines snap to half pixels so the stroke center lands on a pixel boundary. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static PointF ClampToPixel(PointF point, int thickness, bool rotated) + { + // Even thickness: snap to whole pixels. + if ((thickness & 1) == 0) + { + return Point.Truncate(point); + } + + // Odd thickness: snap to half pixels along the perpendicular axis + // so the 1px-wide center row/column aligns with physical pixels. + if (rotated) + { + return Point.Truncate(point) + new Vector2(.5F, 0); + } + + return Point.Truncate(point) + new Vector2(0, .5F); + } + + /// + /// Records the start, end, and thickness of a previously emitted decoration line + /// so that the next adjacent decoration can be stitched seamlessly. + /// + private struct TextDecorationDetails + { + /// Gets or sets the start position of the decoration. + public Vector2 Start { get; set; } + + /// Gets or sets the end position of the decoration. + public Vector2 End { get; set; } + + /// Gets or sets the decoration thickness in pixels. + public float Thickness { get; internal set; } + } +} diff --git a/src/ImageSharp.Drawing/Text/GlyphBuilder.cs b/src/ImageSharp.Drawing/Text/GlyphBuilder.cs new file mode 100644 index 000000000..378591ee3 --- /dev/null +++ b/src/ImageSharp.Drawing/Text/GlyphBuilder.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Drawing.Text; + +/// +/// A rendering surface that Fonts can use to generate shapes. +/// Extends by adding a configurable origin offset +/// so that all captured geometry is translated by the specified amount. +/// +internal class GlyphBuilder : BaseGlyphBuilder +{ + /// + /// Initializes a new instance of the class. + /// + public GlyphBuilder() + : this(Vector2.Zero) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The origin. + public GlyphBuilder(Vector2 origin) => this.Builder.SetOrigin(origin); +} diff --git a/src/ImageSharp.Drawing/Shapes/Text/GlyphLayerInfo.cs b/src/ImageSharp.Drawing/Text/GlyphLayerInfo.cs similarity index 99% rename from src/ImageSharp.Drawing/Shapes/Text/GlyphLayerInfo.cs rename to src/ImageSharp.Drawing/Text/GlyphLayerInfo.cs index da4a0d4f5..0011f932b 100644 --- a/src/ImageSharp.Drawing/Shapes/Text/GlyphLayerInfo.cs +++ b/src/ImageSharp.Drawing/Text/GlyphLayerInfo.cs @@ -100,7 +100,7 @@ private GlyphLayerInfo( /// public GlyphLayerKind Kind { get; } - internal static GlyphLayerInfo Transform(in GlyphLayerInfo info, Matrix3x2 matrix) + internal static GlyphLayerInfo Transform(in GlyphLayerInfo info, Matrix4x4 matrix) => new( info.StartIndex, info.Count, diff --git a/src/ImageSharp.Drawing/Shapes/Text/GlyphLayerKind.cs b/src/ImageSharp.Drawing/Text/GlyphLayerKind.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/Text/GlyphLayerKind.cs rename to src/ImageSharp.Drawing/Text/GlyphLayerKind.cs diff --git a/src/ImageSharp.Drawing/Shapes/Text/GlyphPathCollection.cs b/src/ImageSharp.Drawing/Text/GlyphPathCollection.cs similarity index 99% rename from src/ImageSharp.Drawing/Shapes/Text/GlyphPathCollection.cs rename to src/ImageSharp.Drawing/Text/GlyphPathCollection.cs index 46d08a9f9..b628e75ea 100644 --- a/src/ImageSharp.Drawing/Shapes/Text/GlyphPathCollection.cs +++ b/src/ImageSharp.Drawing/Text/GlyphPathCollection.cs @@ -68,7 +68,7 @@ internal GlyphPathCollection(List paths, List layers) /// /// A new with the matrix applied to it. /// - public GlyphPathCollection Transform(Matrix3x2 matrix) + public GlyphPathCollection Transform(Matrix4x4 matrix) { List transformed = new(this.paths.Count); diff --git a/src/ImageSharp.Drawing/Text/PathGlyphBuilder.cs b/src/ImageSharp.Drawing/Text/PathGlyphBuilder.cs new file mode 100644 index 000000000..1f6144adb --- /dev/null +++ b/src/ImageSharp.Drawing/Text/PathGlyphBuilder.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.Fonts; +using SixLabors.Fonts.Rendering; + +namespace SixLabors.ImageSharp.Drawing.Text; + +/// +/// A rendering surface that Fonts can use to generate shapes by following a path. +/// Each glyph is positioned along the path and rotated to match the path tangent +/// at the glyph's horizontal center. +/// +internal sealed class PathGlyphBuilder : GlyphBuilder +{ + /// + /// The path that glyphs are laid out along. Exposed as + /// to access the method for efficient + /// position + tangent queries. + /// + private readonly IPathInternals path; + + /// + /// Initializes a new instance of the class. + /// + /// The path to render the glyphs along. + public PathGlyphBuilder(IPath path) + { + if (path is IPathInternals internals) + { + this.path = internals; + } + else + { + // Wrap in ComplexPolygon to gain IPathInternals. + this.path = new ComplexPolygon(path); + } + } + + /// + protected override bool BeginGlyph(in FontRectangle bounds, in GlyphRendererParameters parameters) + { + // Translate + rotate the glyph to follow the path. Always returns true because + // path-based glyphs are never cached (each has a unique per-position transform). + this.TransformGlyph(in bounds); + return true; + } + + /// + /// Computes the translation + rotation matrix that places a glyph along the path. + /// The glyph's horizontal center is mapped to the path distance, and the glyph + /// is rotated to match the path tangent at that point. + /// + /// The font-metric bounding rectangle of the glyph. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void TransformGlyph(in FontRectangle bounds) + { + // Query the path at the glyph's horizontal center. + Vector2 half = new(bounds.Width * .5F, 0); + SegmentInfo pathPoint = this.path.PointAlongPath(bounds.Left + half.X); + + // Translate so the glyph's top-left aligns with the path point, + // then rotate around the path point to follow the tangent. + Vector2 translation = (Vector2)pathPoint.Point - bounds.Location - half + new Vector2(0, bounds.Top); + Matrix4x4 matrix = Matrix4x4.CreateTranslation(translation.X, translation.Y, 0) * new Matrix4x4(Matrix3x2.CreateRotation(pathPoint.Angle - MathF.PI, (Vector2)pathPoint.Point)); + + this.Builder.SetTransform(matrix); + } +} diff --git a/src/ImageSharp.Drawing/Shapes/Text/TextBuilder.cs b/src/ImageSharp.Drawing/Text/TextBuilder.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/Text/TextBuilder.cs rename to src/ImageSharp.Drawing/Text/TextBuilder.cs diff --git a/src/ImageSharp.Drawing/Shapes/Text/TextUtilities.cs b/src/ImageSharp.Drawing/Text/TextUtilities.cs similarity index 100% rename from src/ImageSharp.Drawing/Shapes/Text/TextUtilities.cs rename to src/ImageSharp.Drawing/Text/TextUtilities.cs diff --git a/src/ImageSharp.Drawing/Utilities/Intersect.cs b/src/ImageSharp.Drawing/Utilities/Intersect.cs deleted file mode 100644 index e20ed9eb9..000000000 --- a/src/ImageSharp.Drawing/Utilities/Intersect.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Drawing.Utilities; - -/// -/// Lightweight 2D segment intersection helpers for polygon and path processing. -/// -/// -/// This is intentionally small and allocation-free. It favors speed and numerical tolerance -/// over exhaustive classification (e.g., collinear overlap detection), which keeps it fast -/// enough for per-segment scanning in stroking or clipping preparation passes. -/// -internal static class Intersect -{ - // Epsilon used for floating-point tolerance. We treat values within ±Eps as zero. - // This helps avoid instability when segments are nearly parallel or endpoints are - // very close to the intersection boundary. - private const float Eps = 1e-3f; - private const float MinusEps = -Eps; - private const float OnePlusEps = 1 + Eps; - - /// - /// Tests two line segments for intersection, ignoring collinear overlap. - /// - /// Start of segment A. - /// End of segment A. - /// Start of segment B. - /// End of segment B. - /// - /// Receives the intersection point when the segments intersect within tolerance. - /// When no intersection is detected, the value is left unchanged. - /// - /// - /// if the segments intersect within their extents (including endpoints), - /// if they are disjoint or collinear. - /// - /// - /// The method is based on solving two parametric line equations and uses a small epsilon - /// window around [0, 1] to account for floating-point error. Collinear cases are rejected - /// early (crossD ≈ 0) to keep the method fast; callers that need collinear overlap detection - /// must implement that separately. - /// - public static bool LineSegmentToLineSegmentIgnoreCollinear(Vector2 a0, Vector2 a1, Vector2 b0, Vector2 b1, ref Vector2 intersectionPoint) - { - // Direction vectors of the segments. - float dax = a1.X - a0.X; - float day = a1.Y - a0.Y; - float dbx = b1.X - b0.X; - float dby = b1.Y - b0.Y; - - // Cross product of directions. When near zero, the lines are parallel or collinear. - float crossD = (-dbx * day) + (dax * dby); - - // Reject parallel/collinear lines. Collinear overlap is intentionally ignored. - if (crossD is > MinusEps and < Eps) - { - return false; - } - - // Solve for parameters s and t where: - // a0 + t*(a1-a0) = b0 + s*(b1-b0) - float s = ((-day * (a0.X - b0.X)) + (dax * (a0.Y - b0.Y))) / crossD; - float t = ((dbx * (a0.Y - b0.Y)) - (dby * (a0.X - b0.X))) / crossD; - - // If both parameters are within [0,1] (with tolerance), the segments intersect. - if (s > MinusEps && s < OnePlusEps && t > MinusEps && t < OnePlusEps) - { - intersectionPoint.X = a0.X + (t * dax); - intersectionPoint.Y = a0.Y + (t * day); - return true; - } - - return false; - } -} diff --git a/src/ImageSharp.Drawing/Utilities/NumericUtilities.cs b/src/ImageSharp.Drawing/Utilities/NumericUtilities.cs deleted file mode 100644 index b2401ddf9..000000000 --- a/src/ImageSharp.Drawing/Utilities/NumericUtilities.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Drawing.Utilities; - -internal static class NumericUtilities -{ - public static void AddToAllElements(this Span span, float value) - { - ref float current = ref MemoryMarshal.GetReference(span); - ref float max = ref Unsafe.Add(ref current, span.Length); - - if (Vector.IsHardwareAccelerated) - { - int n = span.Length / Vector.Count; - ref Vector currentVec = ref Unsafe.As>(ref current); - ref Vector maxVec = ref Unsafe.Add(ref currentVec, n); - - Vector vecVal = new(value); - while (Unsafe.IsAddressLessThan(ref currentVec, ref maxVec)) - { - currentVec += vecVal; - currentVec = ref Unsafe.Add(ref currentVec, 1); - } - - // current = ref Unsafe.Add(ref current, n * Vector.Count); - current = ref Unsafe.As, float>(ref currentVec); - } - - while (Unsafe.IsAddressLessThan(ref current, ref max)) - { - current += value; - current = ref Unsafe.Add(ref current, 1); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float ClampFloat(float value, float min, float max) - { - if (value >= max) - { - return max; - } - - if (value <= min) - { - return min; - } - - return value; - } -} diff --git a/src/ImageSharp.Drawing/Utilities/ThreadLocalBlenderBuffers.cs b/src/ImageSharp.Drawing/Utilities/ThreadLocalBlenderBuffers.cs deleted file mode 100644 index c3a07c111..000000000 --- a/src/ImageSharp.Drawing/Utilities/ThreadLocalBlenderBuffers.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Utilities; - -internal class ThreadLocalBlenderBuffers : IDisposable - where TPixel : unmanaged, IPixel -{ - private readonly ThreadLocal data; - - // amountBufferOnly:true is for SolidBrush, which doesn't need the overlay buffer (it will be dummy) - public ThreadLocalBlenderBuffers(MemoryAllocator allocator, int scanlineWidth, bool amountBufferOnly = false) - => this.data = new ThreadLocal(() => new BufferOwner(allocator, scanlineWidth, amountBufferOnly), true); - - public Span AmountSpan => this.data.Value!.AmountSpan; - - public Span OverlaySpan => this.data.Value!.OverlaySpan; - - /// - public void Dispose() - { - foreach (BufferOwner d in this.data.Values) - { - d.Dispose(); - } - - this.data.Dispose(); - } - - private sealed class BufferOwner : IDisposable - { - private readonly IMemoryOwner amountBuffer; - private readonly IMemoryOwner? overlayBuffer; - - public BufferOwner(MemoryAllocator allocator, int scanlineLength, bool amountBufferOnly) - { - this.amountBuffer = allocator.Allocate(scanlineLength); - this.overlayBuffer = amountBufferOnly ? null : allocator.Allocate(scanlineLength); - } - - public Span AmountSpan => this.amountBuffer.Memory.Span; - - public Span OverlaySpan - { - get - { - if (this.overlayBuffer != null) - { - return this.overlayBuffer.Memory.Span; - } - - return []; - } - } - - public void Dispose() - { - this.amountBuffer.Dispose(); - this.overlayBuffer?.Dispose(); - } - } -} diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 1f2a992f7..5f5c62e53 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -19,11 +19,6 @@ - - diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawBeziers.cs index 6ff494267..7faef9715 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawBeziers.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawBeziers.cs @@ -4,7 +4,6 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; -using System.Numerics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; @@ -19,43 +18,34 @@ public class DrawBeziers [Benchmark(Baseline = true, Description = "System.Drawing Draw Beziers")] public void DrawPathSystemDrawing() { - using (Bitmap destination = new(800, 800)) - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.Default; - graphics.SmoothingMode = SmoothingMode.AntiAlias; - - using (Pen pen = new(System.Drawing.Color.HotPink, 10)) - { - graphics.DrawBeziers( - pen, - [new SDPointF(10, 500), new SDPointF(30, 10), new SDPointF(240, 30), new SDPointF(300, 500)]); - } + using Bitmap destination = new(800, 800); + using Graphics graphics = Graphics.FromImage(destination); + graphics.InterpolationMode = InterpolationMode.Default; + graphics.SmoothingMode = SmoothingMode.AntiAlias; - using (MemoryStream stream = new()) - { - destination.Save(stream, ImageFormat.Bmp); - } + using (Pen pen = new(System.Drawing.Color.HotPink, 10)) + { + graphics.DrawBeziers( + pen, + [new SDPointF(10, 500), new SDPointF(30, 10), new SDPointF(240, 30), new SDPointF(300, 500)]); } + + using MemoryStream stream = new(); + destination.Save(stream, ImageFormat.Bmp); } [Benchmark(Description = "ImageSharp Draw Beziers")] public void DrawLinesCore() { - using (Image image = new(800, 800)) - { - image.Mutate(x => x.DrawBeziers( - Color.HotPink, - 10, - new Vector2(10, 500), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 500))); + using Image image = new(800, 800); + image.Mutate(x => x.ProcessWithCanvas(canvas => canvas.DrawBezier( + Processing.Pens.Solid(Color.HotPink, 10), + new PointF(10, 500), + new PointF(30, 10), + new PointF(240, 30), + new PointF(300, 500)))); - using (MemoryStream stream = new()) - { - image.SaveAsBmp(stream); - } - } + using MemoryStream stream = new(); + image.SaveAsBmp(stream); } } diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawPolygon.cs index c3080014a..02f65b8bd 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawPolygon.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawPolygon.cs @@ -8,7 +8,7 @@ using GeoJSON.Net.Feature; using Newtonsoft.Json; using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; +using SixLabors.ImageSharp.Drawing.Processing.Backends; using SixLabors.ImageSharp.Drawing.Tests; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -38,6 +38,11 @@ public abstract class DrawPolygon private IPath imageSharpPath; private IPath strokedImageSharpPath; + private WebGPUDrawingBackend webGpuBackend; + private Configuration webGpuConfiguration; + private NativeCanvasFrame webGpuNativeFrame; + private nint webGpuNativeTextureHandle; + private nint webGpuNativeTextureViewHandle; protected abstract int Width { get; } @@ -46,7 +51,7 @@ public abstract class DrawPolygon protected abstract float Thickness { get; } protected virtual PointF[][] GetPoints(FeatureCollection features) => - features.Features.SelectMany(f => PolygonFactory.GetGeoJsonPoints(f, Matrix3x2.CreateScale(60, 60))).ToArray(); + [.. features.Features.SelectMany(f => PolygonFactory.GetGeoJsonPoints(f, Matrix4x4.CreateScale(60, 60, 1)))]; [GlobalSetup] public void Setup() @@ -107,6 +112,25 @@ public void Setup() this.image = new Image(this.Width, this.Height); this.isPen = new SolidPen(Color.White, this.Thickness); this.strokedImageSharpPath = this.isPen.GeneratePath(this.imageSharpPath); + this.webGpuBackend = new WebGPUDrawingBackend(); + this.webGpuConfiguration = Configuration.Default.Clone(); + this.webGpuConfiguration.SetDrawingBackend(this.webGpuBackend); + + if (!WebGPUTestNativeSurfaceAllocator.TryCreate( + this.Width, + this.Height, + out NativeSurface nativeSurface, + out this.webGpuNativeTextureHandle, + out this.webGpuNativeTextureViewHandle, + out string nativeSurfaceError)) + { + throw new InvalidOperationException( + $"Unable to create benchmark native WebGPU target. Error='{nativeSurfaceError}'."); + } + + this.webGpuNativeFrame = new NativeCanvasFrame( + new Rectangle(0, 0, this.Width, this.Height), + nativeSurface); this.sdBitmap = new Bitmap(this.Width, this.Height); this.sdGraphics = Graphics.FromImage(this.sdBitmap); @@ -148,55 +172,33 @@ public void Cleanup() this.skPath.Dispose(); this.image.Dispose(); + WebGPUTestNativeSurfaceAllocator.Release( + this.webGpuNativeTextureHandle, + this.webGpuNativeTextureViewHandle); + this.webGpuNativeTextureHandle = 0; + this.webGpuNativeTextureViewHandle = 0; + this.webGpuBackend.Dispose(); } - [Benchmark] - public void SystemDrawing() - => this.sdGraphics.DrawPath(this.sdPen, this.sdPath); - - // Keep explicit scanline rasterizer path for side-by-side comparison now that tiled is default. - [Benchmark] - public void ImageSharpCombinedPathsScanlineRasterizer() - => this.image.Mutate(c => c.SetRasterizer(ScanlineRasterizer.Instance).Draw(this.isPen, this.imageSharpPath)); - - [Benchmark] - public void ImageSharpSeparatePathsScanlineRasterizer() - => this.image.Mutate( - c => - { - // Keep explicit scanline rasterizer path for side-by-side comparison now that tiled is default. - c.SetRasterizer(ScanlineRasterizer.Instance); - foreach (PointF[] loop in this.points) - { - c.DrawPolygon(Color.White, this.Thickness, loop); - } - }); - - // Tiled is now the framework default rasterizer path. - [Benchmark] - public void ImageSharpCombinedPathsTiled() - => this.image.Mutate(c => c.Draw(this.isPen, this.imageSharpPath)); - - [Benchmark] - public void ImageSharpSeparatePathsTiled() - => this.image.Mutate( - c => - { - foreach (PointF[] loop in this.points) - { - c.DrawPolygon(Color.White, this.Thickness, loop); - } - }); - [Benchmark(Baseline = true)] public void SkiaSharp() => this.skSurface.Canvas.DrawPath(this.skPath, this.skPaint); [Benchmark] - public IPath ImageSharpStrokeAndClip() => this.isPen.GeneratePath(this.imageSharpPath); + public void SystemDrawing() + => this.sdGraphics.DrawPath(this.sdPen, this.sdPath); + + [Benchmark] + public void ImageSharp() + => this.image.Mutate(c => c.ProcessWithCanvas(canvas => canvas.Draw(this.isPen, this.imageSharpPath))); [Benchmark] - public void FillPolygon() => this.image.Mutate(c => c.Fill(Color.White, this.strokedImageSharpPath)); + public void ImageSharpWebGPUNativeSurface() + { + using DrawingCanvas canvas = new(this.webGpuConfiguration, this.webGpuNativeFrame, new DrawingOptions()); + canvas.Draw(this.isPen, this.imageSharpPath); + canvas.Flush(); + } } public class DrawPolygonAll : DrawPolygon @@ -220,9 +222,9 @@ protected override PointF[][] GetPoints(FeatureCollection features) { Feature state = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); - Matrix3x2 transform = Matrix3x2.CreateTranslation(-87, -54) - * Matrix3x2.CreateScale(60, 60); - return PolygonFactory.GetGeoJsonPoints(state, transform).ToArray(); + Matrix4x4 transform = Matrix4x4.CreateTranslation(-87, -54, 0) + * Matrix4x4.CreateScale(60, 60, 1); + return [.. PolygonFactory.GetGeoJsonPoints(state, transform)]; } } diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawText.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawText.cs index 21dedf417..201eeed0f 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawText.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawText.cs @@ -84,7 +84,8 @@ public void ImageSharp() Origin = new PointF(10, 10) }; - this.image.Mutate(x => x.DrawText(textOptions, this.TextToRender, Processing.Brushes.Solid(Color.HotPink))); + this.image.Mutate(x => x.ProcessWithCanvas( + canvas => canvas.DrawText(textOptions, this.TextToRender, Processing.Brushes.Solid(Color.HotPink), pen: null))); } [Benchmark(Baseline = true)] diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawTextOutline.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawTextOutline.cs index 245d7e852..48e9e95fd 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawTextOutline.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawTextOutline.cs @@ -60,10 +60,11 @@ public void DrawTextCore() Origin = new PointF(10, 10) }; - image.Mutate(x => x.DrawText( + image.Mutate(x => x.ProcessWithCanvas(canvas => canvas.DrawText( textOptions, this.TextToRender, - Processing.Pens.Solid(Color.HotPink, 10))); + brush: null, + pen: Processing.Pens.Solid(Color.HotPink, 10)))); } [Benchmark(Description = "ImageSharp Draw Text Outline - Naive")] @@ -79,17 +80,17 @@ public void DrawTextCoreOld() }; image.Mutate( - x => DrawTextOldVersion( - x, + x => x.ProcessWithCanvas(canvas => DrawTextOldVersion( + canvas, new DrawingOptions { GraphicsOptions = { Antialias = true } }, textOptions, this.TextToRender, null, - Processing.Pens.Solid(Color.HotPink, 10))); + Processing.Pens.Solid(Color.HotPink, 10)))); } - static IImageProcessingContext DrawTextOldVersion( - IImageProcessingContext source, + static void DrawTextOldVersion( + IDrawingCanvas canvas, DrawingOptions options, TextOptions textOptions, string text, @@ -97,19 +98,23 @@ static IImageProcessingContext DrawTextOldVersion( Pen pen) { IPathCollection glyphs = TextBuilder.GeneratePaths(text, textOptions); - - DrawingOptions pathOptions = new() { GraphicsOptions = options.GraphicsOptions }; - if (brush != null) + int saveCount = canvas.Save(options); + try { - source.Fill(pathOptions, brush, glyphs); + if (brush != null) + { + canvas.Fill(brush, glyphs); + } + + if (pen != null) + { + canvas.Draw(pen, glyphs); + } } - - if (pen != null) + finally { - source.Draw(pathOptions, pen, glyphs); + canvas.RestoreTo(saveCount); } - - return source; } } diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawTextRepeatedGlyphs.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawTextRepeatedGlyphs.cs new file mode 100644 index 000000000..4f9403a6d --- /dev/null +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/DrawTextRepeatedGlyphs.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Processing.Backends; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Benchmarks.Drawing; + +[MemoryDiagnoser] +[WarmupCount(5)] +[IterationCount(5)] +public class DrawTextRepeatedGlyphs +{ + public const int Width = 1200; + public const int Height = 280; + + private readonly DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions + { + Antialias = true + } + }; + + private readonly Brush brush = Brushes.Solid(Color.HotPink); + + private Configuration defaultConfiguration; + private Image defaultImage; + private WebGPUDrawingBackend webGpuBackend; + private Configuration webGpuConfiguration; + private NativeCanvasFrame webGpuNativeFrame; + private nint webGpuNativeTextureHandle; + private nint webGpuNativeTextureViewHandle; + private RichTextOptions textOptions; + private string text; + + [Params(200, 1000)] + public int GlyphCount { get; set; } + + [GlobalSetup] + public void Setup() + { + // Tiled rasterization benefits from a warmed worker pool. Doing this once in setup + // reduces first-iteration noise without affecting per-method correctness. + ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads); + int desiredWorkerThreads = Math.Max(minWorkerThreads, Environment.ProcessorCount); + ThreadPool.SetMinThreads(desiredWorkerThreads, minCompletionPortThreads); + Parallel.For(0, desiredWorkerThreads, static _ => { }); + + Font font = SystemFonts.CreateFont("Arial", 48); + this.textOptions = new RichTextOptions(font) + { + Origin = new PointF(8, 8), + WrappingLength = Width - 16 + }; + + this.defaultConfiguration = Configuration.Default; + this.defaultImage = new Image(Width, Height); + this.webGpuBackend = new WebGPUDrawingBackend(); + this.webGpuConfiguration = Configuration.Default.Clone(); + this.webGpuConfiguration.SetDrawingBackend(this.webGpuBackend); + + if (!WebGPUTestNativeSurfaceAllocator.TryCreate( + Width, + Height, + out NativeSurface nativeSurface, + out this.webGpuNativeTextureHandle, + out this.webGpuNativeTextureViewHandle, + out string nativeSurfaceError)) + { + throw new InvalidOperationException( + $"Unable to create benchmark native WebGPU target. Error='{nativeSurfaceError}'."); + } + + this.webGpuNativeFrame = new NativeCanvasFrame( + new Rectangle(0, 0, Width, Height), + nativeSurface); + + this.text = new string('A', this.GlyphCount); + } + + [GlobalCleanup] + public void Cleanup() + { + this.defaultImage.Dispose(); + WebGPUTestNativeSurfaceAllocator.Release( + this.webGpuNativeTextureHandle, + this.webGpuNativeTextureViewHandle); + this.webGpuNativeTextureHandle = 0; + this.webGpuNativeTextureViewHandle = 0; + this.webGpuBackend.Dispose(); + } + + [Benchmark(Baseline = true, Description = "DrawingCanvas Default Backend")] + public void DrawingCanvasDefaultBackend() + { + MemoryCanvasFrame frame = new(GetFrameRegion(this.defaultImage)); + + using DrawingCanvas canvas = new(this.defaultConfiguration, frame, this.drawingOptions); + canvas.DrawText(this.textOptions, this.text, this.brush, null); + canvas.Flush(); + } + + [Benchmark(Description = "DrawingCanvas WebGPU Backend (NativeSurface)")] + public void DrawingCanvasWebGPUBackendNativeSurface() + { + using DrawingCanvas canvas = new(this.webGpuConfiguration, this.webGpuNativeFrame, this.drawingOptions); + canvas.DrawText(this.textOptions, this.text, this.brush, null); + canvas.Flush(); + } + + private static Buffer2DRegion GetFrameRegion(Image image) + => new(image.Frames.RootFrame.PixelBuffer, new Rectangle(0, 0, image.Width, image.Height)); +} diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/EllipseStressTest.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/EllipseStressTest.cs index f5c356061..7aed5cd8d 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/EllipseStressTest.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/EllipseStressTest.cs @@ -23,22 +23,23 @@ public class EllipseStressTest [Benchmark] public void DrawImageSharp() - { - for (int i = 0; i < 20_000; i++) - { - Color brushColor = Color.FromPixel(new Rgba32((byte)this.Rand(255), (byte)this.Rand(255), (byte)this.Rand(255), (byte)this.Rand(255))); - Color penColor = Color.FromPixel(new Rgba32((byte)this.Rand(255), (byte)this.Rand(255), (byte)this.Rand(255), (byte)this.Rand(255))); - - float r = this.Rand(20f) + 1f; - float x = this.Rand(this.width); - float y = this.Rand(this.height); - EllipsePolygon ellipse = new(new PointF(x, y), r); - this.image.Mutate( - m => - m.Fill(Brushes.Solid(brushColor), ellipse) - .Draw(Pens.Solid(penColor, this.Rand(5)), ellipse)); - } - } + => this.image.Mutate( + m => m.ProcessWithCanvas(canvas => + { + for (int i = 0; i < 20_000; i++) + { + Color brushColor = Color.FromPixel(new Rgba32((byte)this.Rand(255), (byte)this.Rand(255), (byte)this.Rand(255), (byte)this.Rand(255))); + Color penColor = Color.FromPixel(new Rgba32((byte)this.Rand(255), (byte)this.Rand(255), (byte)this.Rand(255), (byte)this.Rand(255))); + + float r = this.Rand(20f) + 1f; + float x = this.Rand(this.width); + float y = this.Rand(this.height); + EllipsePolygon ellipse = new(new PointF(x, y), r); + + canvas.Fill(Brushes.Solid(brushColor), ellipse); + canvas.Draw(Pens.Solid(penColor, this.Rand(5)), ellipse); + } + })); [GlobalCleanup] public void Cleanup() @@ -49,5 +50,5 @@ public void Cleanup() [MethodImpl(MethodImplOptions.AggressiveInlining)] private float Rand(float x) - => ((float)(((this.random.Next() << 15) | this.random.Next()) & 0x3FFFFFFF) % 1000000) * x / 1000000f; + => Math.Max(0.5f, ((float)(((this.random.Next() << 15) | this.random.Next()) & 0x3FFFFFFF) % 1000000) * x / 1000000f); } diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillPathGradientBrush.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillPathGradientBrush.cs index acac46b09..c83b5880d 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillPathGradientBrush.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillPathGradientBrush.cs @@ -31,6 +31,6 @@ public void FillGradientBrush_ImageSharp() PathGradientBrush brush = new(points, colors, Color.White); - this.image.Mutate(x => x.Fill(brush)); + this.image.Mutate(x => x.ProcessWithCanvas(canvas => canvas.Fill(brush))); } } diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillPolygon.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillPolygon.cs index 60ba9b199..25b22948c 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillPolygon.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillPolygon.cs @@ -36,9 +36,7 @@ public abstract class FillPolygon protected abstract int Height { get; } protected virtual PointF[][] GetPoints(FeatureCollection features) - => features.Features - .SelectMany(f => PolygonFactory.GetGeoJsonPoints(f, Matrix3x2.CreateScale(60, 60))) - .ToArray(); + => [.. features.Features.SelectMany(f => PolygonFactory.GetGeoJsonPoints(f, Matrix4x4.CreateScale(60, 60, 1)))]; [GlobalSetup] public void Setup() @@ -48,9 +46,9 @@ public void Setup() FeatureCollection featureCollection = JsonConvert.DeserializeObject(jsonContent); this.points = this.GetPoints(featureCollection); - this.polygons = this.points.Select(pts => new Polygon(new LinearLineSegment(pts))).ToArray(); + this.polygons = [.. this.points.Select(pts => new Polygon(new LinearLineSegment(pts)))]; - this.sdPoints = this.points.Select(pts => pts.Select(p => new SDPointF(p.X, p.Y)).ToArray()).ToArray(); + this.sdPoints = [.. this.points.Select(pts => pts.Select(p => new SDPointF(p.X, p.Y)).ToArray())]; this.skPaths = []; foreach (PointF[] ptArr in this.points.Where(pts => pts.Length > 2)) @@ -102,13 +100,13 @@ public void SystemDrawing() [Benchmark] public void ImageSharp() - => this.image.Mutate(c => + => this.image.Mutate(c => c.ProcessWithCanvas(canvas => { foreach (Polygon polygon in this.polygons) { - c.Fill(Color.White, polygon); + canvas.Fill(Processing.Brushes.Solid(Color.White), polygon); } - }); + })); [Benchmark(Baseline = true)] public void SkiaSharp() @@ -144,9 +142,9 @@ protected override PointF[][] GetPoints(FeatureCollection features) { Feature state = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); - Matrix3x2 transform = Matrix3x2.CreateTranslation(-87, -54) - * Matrix3x2.CreateScale(60, 60); - return PolygonFactory.GetGeoJsonPoints(state, transform).ToArray(); + Matrix4x4 transform = Matrix4x4.CreateTranslation(-87, -54, 0) + * Matrix4x4.CreateScale(60, 60, 1); + return [.. PolygonFactory.GetGeoJsonPoints(state, transform)]; } // ** 11/13/2020 @ Anton's PC *** @@ -174,8 +172,8 @@ protected override PointF[][] GetPoints(FeatureCollection features) { Feature state = features.Features.Single(f => (string)f.Properties["NAME"] == "Utah"); - Matrix3x2 transform = Matrix3x2.CreateTranslation(-60, -40) - * Matrix3x2.CreateScale(60, 60); - return PolygonFactory.GetGeoJsonPoints(state, transform).ToArray(); + Matrix4x4 transform = Matrix4x4.CreateTranslation(-60, -40, 0) + * Matrix4x4.CreateScale(60, 60, 1); + return [.. PolygonFactory.GetGeoJsonPoints(state, transform)]; } } diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillRectangle.cs index b5d04ec28..d0be4f328 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillRectangle.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillRectangle.cs @@ -3,7 +3,6 @@ using System.Drawing; using System.Drawing.Drawing2D; -using System.Numerics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; @@ -34,7 +33,8 @@ public Size FillRectangleCore() { using (Image image = new(800, 800)) { - image.Mutate(x => x.Fill(Color.HotPink, new Rectangle(10, 10, 190, 140))); + image.Mutate(x => x.ProcessWithCanvas( + canvas => canvas.Fill(Processing.Brushes.Solid(Color.HotPink), new Rectangle(10, 10, 190, 140)))); return new Size(image.Width, image.Height); } @@ -45,12 +45,16 @@ public Size FillPolygonCore() { using (Image image = new(800, 800)) { - image.Mutate(x => x.FillPolygon( - Color.HotPink, - new Vector2(10, 10), - new Vector2(200, 10), - new Vector2(200, 150), - new Vector2(10, 150))); + image.Mutate(x => x.ProcessWithCanvas( + canvas => canvas.Fill( + Processing.Brushes.Solid(Color.HotPink), + new Polygon( + [ + new PointF(10, 10), + new PointF(200, 10), + new PointF(200, 150), + new PointF(10, 150) + ])))); return new Size(image.Width, image.Height); } diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillWithPattern.cs index 63fc8bcc6..b0b250c4b 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillWithPattern.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Drawing/FillWithPattern.cs @@ -17,34 +17,27 @@ public class FillWithPattern [Benchmark(Baseline = true, Description = "System.Drawing Fill with Pattern")] public void DrawPatternPolygonSystemDrawing() { - using (Bitmap destination = new(800, 800)) - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.SmoothingMode = SmoothingMode.AntiAlias; - - using (HatchBrush brush = new(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink)) - { - graphics.FillRectangle(brush, new SDRectangle(0, 0, 800, 800)); // can't find a way to flood fill with a brush - } + using Bitmap destination = new(800, 800); + using Graphics graphics = Graphics.FromImage(destination); + graphics.SmoothingMode = SmoothingMode.AntiAlias; - using (MemoryStream stream = new()) - { - destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); - } + using (HatchBrush brush = new(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink)) + { + graphics.FillRectangle(brush, new SDRectangle(0, 0, 800, 800)); // can't find a way to flood fill with a brush } + + using MemoryStream stream = new(); + destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); } [Benchmark(Description = "ImageSharp Fill with Pattern")] public void DrawPatternPolygon3Core() { - using (Image image = new(800, 800)) - { - image.Mutate(x => x.Fill(CoreBrushes.BackwardDiagonal(Color.HotPink))); + using Image image = new(800, 800); + image.Mutate(x => x.ProcessWithCanvas( + canvas => canvas.Fill(CoreBrushes.BackwardDiagonal(Color.HotPink)))); - using (MemoryStream stream = new()) - { - image.SaveAsBmp(stream); - } - } + using MemoryStream stream = new(); + image.SaveAsBmp(stream); } } diff --git a/tests/ImageSharp.Drawing.Benchmarks/Drawing/Rounding.cs b/tests/ImageSharp.Drawing.Benchmarks/Drawing/Rounding.cs deleted file mode 100644 index 45f09159f..000000000 --- a/tests/ImageSharp.Drawing.Benchmarks/Drawing/Rounding.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Drawing.Benchmarks.Drawing; -public class Rounding -{ - private PointF[] vertices; - private float[] destination; - private float[] destinationSse41; - private float[] destinationAvx; - - [GlobalSetup] - public void Setup() - { - this.vertices = new PointF[1000]; - this.destination = new float[this.vertices.Length]; - this.destinationSse41 = new float[this.vertices.Length]; - this.destinationAvx = new float[this.vertices.Length]; - Random r = new(42); - for (int i = 0; i < this.vertices.Length; i++) - { - this.vertices[i] = new PointF((float)r.NextDouble(), (float)r.NextDouble()); - } - } - - [Benchmark] - public void RoundYAvx() => RoundYAvx(this.vertices, this.destinationAvx, 16); - - [Benchmark] - public void RoundYSse41() => RoundYSse41(this.vertices, this.destinationSse41, 16); - - [Benchmark(Baseline = true)] - public void RoundY() => RoundY(this.vertices, this.destination, 16); - - private static void RoundYAvx(ReadOnlySpan vertices, Span destination, float subsamplingRatio) - { - int ri = 0; - if (Avx.IsSupported) - { - // If the length of the input buffer as a float array is a multiple of 16, we can use AVX instructions: - int verticesLengthInFloats = vertices.Length * 2; - int vector256FloatCount_x2 = Vector256.Count * 2; - int remainder = verticesLengthInFloats % vector256FloatCount_x2; - int verticesLength = verticesLengthInFloats - remainder; - - if (verticesLength > 0) - { - ri = vertices.Length - (remainder / 2); - nint maxIterations = verticesLength / (Vector256.Count * 2); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vertices)); - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - Vector256 ssRatio = Vector256.Create(subsamplingRatio); - Vector256 inverseSsRatio = Vector256.Create(1F / subsamplingRatio); - - // For every 1 vector we add to the destination we read 2 from the vertices. - for (nint i = 0, j = 0; i < maxIterations; i++, j += 2) - { - // Load 8 PointF - Vector256 points1 = Unsafe.Add(ref sourceBase, j); - Vector256 points2 = Unsafe.Add(ref sourceBase, j + 1); - - // Shuffle the points to group the Y properties - Vector128 points1Y = Sse.Shuffle(points1.GetLower(), points1.GetUpper(), 0b11_01_11_01); - Vector128 points2Y = Sse.Shuffle(points2.GetLower(), points2.GetUpper(), 0b11_01_11_01); - Vector256 pointsY = Vector256.Create(points1Y, points2Y); - - // Multiply by the subsampling ratio, round, then multiply by the inverted subsampling ratio and assign. - // https://www.ocf.berkeley.edu/~horie/rounding.html - Vector256 rounded = Avx.RoundToPositiveInfinity(Avx.Multiply(pointsY, ssRatio)); - Unsafe.Add(ref destinationBase, i) = Avx.Multiply(rounded, inverseSsRatio); - } - } - } - - for (; ri < vertices.Length; ri++) - { - destination[ri] = MathF.Round(vertices[ri].Y * subsamplingRatio, MidpointRounding.AwayFromZero) / subsamplingRatio; - } - } - - private static void RoundYSse41(ReadOnlySpan vertices, Span destination, float subsamplingRatio) - { - int ri = 0; - if (Sse41.IsSupported) - { - // If the length of the input buffer as a float array is a multiple of 8, we can use Sse instructions: - int verticesLengthInFloats = vertices.Length * 2; - int vector128FloatCount_x2 = Vector128.Count * 2; - int remainder = verticesLengthInFloats % vector128FloatCount_x2; - int verticesLength = verticesLengthInFloats - remainder; - - if (verticesLength > 0) - { - ri = vertices.Length - (remainder / 2); - nint maxIterations = verticesLength / (Vector128.Count * 2); - ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vertices)); - ref Vector128 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - - Vector128 ssRatio = Vector128.Create(subsamplingRatio); - Vector128 inverseSsRatio = Vector128.Create(1F / subsamplingRatio); - - // For every 1 vector we add to the destination we read 2 from the vertices. - for (nint i = 0, j = 0; i < maxIterations; i++, j += 2) - { - // Load 4 PointF - Vector128 points1 = Unsafe.Add(ref sourceBase, j); - Vector128 points2 = Unsafe.Add(ref sourceBase, j + 1); - - // Shuffle the points to group the Y properties - Vector128 points1Y = Sse.Shuffle(points1, points1, 0b11_01_11_01); - Vector128 points2Y = Sse.Shuffle(points2, points2, 0b11_01_11_01); - Vector128 pointsY = Vector128.Create(points1Y.GetLower(), points2Y.GetLower()); - - // Multiply by the subsampling ratio, round, then multiply by the inverted subsampling ratio and assign. - // https://www.ocf.berkeley.edu/~horie/rounding.html - Vector128 rounded = Sse41.RoundToPositiveInfinity(Sse.Multiply(pointsY, ssRatio)); - Unsafe.Add(ref destinationBase, i) = Sse.Multiply(rounded, inverseSsRatio); - } - } - } - - for (; ri < vertices.Length; ri++) - { - destination[ri] = MathF.Round(vertices[ri].Y * subsamplingRatio, MidpointRounding.AwayFromZero) / subsamplingRatio; - } - } - - private static void RoundY(ReadOnlySpan vertices, Span destination, float subsamplingRatio) - { - int ri = 0; - for (; ri < vertices.Length; ri++) - { - destination[ri] = MathF.Round(vertices[ri].Y * subsamplingRatio, MidpointRounding.AwayFromZero) / subsamplingRatio; - } - } -} diff --git a/tests/ImageSharp.Drawing.Benchmarks/ImageSharp.Drawing.Benchmarks.csproj b/tests/ImageSharp.Drawing.Benchmarks/ImageSharp.Drawing.Benchmarks.csproj index 0a2f32ce1..5b9dd9a66 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/ImageSharp.Drawing.Benchmarks.csproj +++ b/tests/ImageSharp.Drawing.Benchmarks/ImageSharp.Drawing.Benchmarks.csproj @@ -9,11 +9,16 @@ - + + - CA1822 - CA1416 + + + CA1822;CA1416;CA1001;CS0029;CA1861;CA2201 @@ -40,6 +45,7 @@ + diff --git a/tests/ImageSharp.Drawing.Benchmarks/Program.cs b/tests/ImageSharp.Drawing.Benchmarks/Program.cs index 9822ba4ea..0a4dd9138 100644 --- a/tests/ImageSharp.Drawing.Benchmarks/Program.cs +++ b/tests/ImageSharp.Drawing.Benchmarks/Program.cs @@ -27,7 +27,7 @@ public InProcessConfig() this.AddJob( Job.Default .WithLaunchCount(3) - .WithWarmupCount(15) + .WithWarmupCount(40) .WithIterationCount(40) .WithToolchain(InProcessEmitToolchain.Instance)); } diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/ClearSolidBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/ClearSolidBrushTests.cs deleted file mode 100644 index cc2fc17f9..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/ClearSolidBrushTests.cs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class ClearSolidBrushTests -{ - [Theory] - [WithBlankImage(1, 1, PixelTypes.Rgba32)] - [WithBlankImage(7, 4, PixelTypes.Rgba32)] - [WithBlankImage(16, 7, PixelTypes.Rgba32)] - [WithBlankImage(33, 32, PixelTypes.Rgba32)] - [WithBlankImage(400, 500, PixelTypes.Rgba32)] - public void DoesNotDependOnSize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color color = Color.HotPink; - image.Mutate(c => c.Clear(color)); - - image.DebugSave(provider, appendPixelTypeToFileName: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithBlankImage(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] - public void DoesNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color color = Color.HotPink; - image.Mutate(c => c.Clear(color)); - - image.DebugSave(provider, appendSourceFileOrDescription: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] - [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] - public void WhenColorIsOpaque_OverridePreviousColor( - TestImageProvider provider, - string newColorName) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color color = TestUtils.GetColorByName(newColorName); - image.Mutate(c => c.Clear(color)); - - image.DebugSave( - provider, - newColorName, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] - [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] - public void ClearAlwaysOverridesPreviousColor( - TestImageProvider provider, - string newColorName) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color color = TestUtils.GetColorByName(newColorName); - color = color.WithAlpha(0.5f); - - image.Mutate(c => c.Clear(color)); - - image.DebugSave( - provider, - newColorName, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] - public void FillRegion(TestImageProvider provider, int x0, int y0, int w, int h) - where TPixel : unmanaged, IPixel - { - FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; - RectangleF region = new(x0, y0, w, h); - Color color = TestUtils.GetColorByName("Blue"); - - provider.RunValidatingProcessorTest(c => c.Clear(color, region), testDetails, ImageComparer.Exact); - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] - public void FillRegion_WorksOnWrappedMemoryImage( - TestImageProvider provider, - int x0, - int y0, - int w, - int h) - where TPixel : unmanaged, IPixel - { - FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; - RectangleF region = new(x0, y0, w, h); - Color color = TestUtils.GetColorByName("Blue"); - - provider.RunValidatingProcessorTestOnWrappedMemoryImage( - c => c.Clear(color, region), - testDetails, - ImageComparer.Exact, - useReferenceOutputFrom: nameof(this.FillRegion)); - } - - public static readonly TheoryData BlendData = - new() - { - { false, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, - { false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, - { false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, - }; -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/ClipTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/ClipTests.cs deleted file mode 100644 index c8892b878..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/ClipTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class ClipTests -{ - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 0, 0, 0.5)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, -20, -20, 0.5)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, -20, -100, 0.5)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 20, 20, 0.5)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 40, 60, 0.2)] - public void Clip(TestImageProvider provider, float dx, float dy, float sizeMult) - where TPixel : unmanaged, IPixel - { - FormattableString testDetails = $"offset_x{dx}_y{dy}"; - provider.RunValidatingProcessorTest( - x => - { - Size size = x.GetCurrentSize(); - int outerRadii = (int)(Math.Min(size.Width, size.Height) * sizeMult); - Star star = new(new PointF(size.Width / 2, size.Height / 2), 5, outerRadii / 2, outerRadii); - - Matrix3x2 builder = Matrix3x2.CreateTranslation(new Vector2(dx, dy)); - x.Clip(star.Transform(builder), x => x.DetectEdges()); - }, - testOutputDetails: testDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] - public void Clip_ConstrainsOperationToClipBounds(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest( - x => - { - Size size = x.GetCurrentSize(); - RectangleF rect = new(0, 0, size.Width / 2, size.Height / 2); - RectangularPolygon clipRect = new(rect); - x.Clip(clipRect, ctx => ctx.Flip(FlipMode.Vertical)); - }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - [Fact] - public void Issue250_Vertical_Horizontal_Count_Should_Match() - { - PathCollection clip = new(new RectangularPolygon(new PointF(24, 16), new PointF(777, 385))); - - Path vert = new(new LinearLineSegment(new PointF(26, 384), new PointF(26, 163))); - Path horiz = new(new LinearLineSegment(new PointF(26, 163), new PointF(176, 163))); - - IPath reverse = vert.Clip(clip); - IEnumerable> result1 = vert.Clip(reverse).Flatten().Select(x => x.Points); - - reverse = horiz.Clip(clip); - IEnumerable> result2 = horiz.Clip(reverse).Flatten().Select(x => x.Points); - - bool same = result1.Count() == result2.Count(); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ComputeLength.cs b/tests/ImageSharp.Drawing.Tests/Drawing/ComputeLength.cs similarity index 91% rename from tests/ImageSharp.Drawing.Tests/Drawing/Paths/ComputeLength.cs rename to tests/ImageSharp.Drawing.Tests/Drawing/ComputeLength.cs index d7d7bb4a1..cba7efcec 100644 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ComputeLength.cs +++ b/tests/ImageSharp.Drawing.Tests/Drawing/ComputeLength.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; +namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; public class ComputeLength { diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawBezierTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawBezierTests.cs deleted file mode 100644 index 550590187..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawBezierTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class DrawBezierTests -{ - public static readonly TheoryData DrawPathData - = new() - { - { "White", 255, 1.5f }, - { "Red", 255, 3 }, - { "HotPink", 255, 5 }, - { "HotPink", 150, 5 }, - { "White", 255, 15 }, - }; - - [Theory] - [WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)] - public void DrawBeziers(TestImageProvider provider, string colorName, byte alpha, float thickness) - where TPixel : unmanaged, IPixel - { - PointF[] points = - [ - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - ]; - - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha / 255F); - - FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; - - provider.RunValidatingProcessorTest( - x => x.DrawBeziers(color, 5f, points), - testDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawComplexPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawComplexPolygonTests.cs deleted file mode 100644 index 34026ab54..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawComplexPolygonTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class DrawComplexPolygonTests -{ - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, true, false, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, true, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, true)] - public void DrawComplexPolygon(TestImageProvider provider, bool overlap, bool transparent, bool dashed) - where TPixel : unmanaged, IPixel - { - Polygon simplePath = new(new LinearLineSegment( - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300))); - - Polygon hole1 = new(new LinearLineSegment( - new Vector2(37, 85), - overlap ? new Vector2(130, 40) : new Vector2(93, 85), - new Vector2(65, 137))); - - IPath clipped = simplePath.Clip(hole1); - - Color color = Color.White; - if (transparent) - { - color = color.WithAlpha(150 / 255F); - } - - string testDetails = string.Empty; - if (overlap) - { - testDetails += "_Overlap"; - } - - if (transparent) - { - testDetails += "_Transparent"; - } - - if (dashed) - { - testDetails += "_Dashed"; - } - - Pen pen = dashed ? Pens.Dash(color, 5f) : Pens.Solid(color, 5f); - - // clipped = new RectangularPolygon(RectangleF.FromLTRB(60, 260, 200, 280)); - - provider.RunValidatingProcessorTest( - x => x.Draw(pen, clipped), - testDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawLinesTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawLinesTests.cs deleted file mode 100644 index b2ba8752b..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawLinesTests.cs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class DrawLinesTests -{ - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)] - public void DrawLines_Simple(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - SolidPen pen = new(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithSolidFilledImages(30, 30, "White", PixelTypes.Rgba32, 1f, true)] - [WithSolidFilledImages(30, 30, "White", PixelTypes.Rgba32, 5f, true)] - [WithSolidFilledImages(30, 30, "White", PixelTypes.Rgba32, 1f, false)] - [WithSolidFilledImages(30, 30, "White", PixelTypes.Rgba32, 5f, false)] - public void DrawLinesInvalidPoints(TestImageProvider provider, float thickness, bool antialias) - where TPixel : unmanaged, IPixel - { - SolidPen pen = new(Color.Black, thickness); - PointF[] path = [new Vector2(15f, 15f), new Vector2(15f, 15f)]; - - GraphicsOptions options = new() - { - Antialias = antialias - }; - - string aa = antialias ? string.Empty : "_NoAntialias"; - FormattableString outputDetails = $"T({thickness}){aa}"; - - provider.RunValidatingProcessorTest( - c => c.SetGraphicsOptions(options).DrawLine(pen, path), - outputDetails, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] - public void DrawLines_Dash(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = Pens.Dash(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "LightGreen", 1f, 5, false)] - public void DrawLines_Dot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = Pens.Dot(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, false)] - public void DrawLines_DashDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = Pens.DashDot(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Black", 1f, 5, false)] - public void DrawLines_DashDotDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - Pen pen = Pens.DashDotDot(color, thickness); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, true)] - public void DrawLines_EndCapRound(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - PatternPen pen = new(new PenOptions(color, thickness, [3f, 3f]) - { - StrokeOptions = new StrokeOptions { LineCap = LineCap.Round }, - }); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, true)] - public void DrawLines_EndCapButt(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) -where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - PatternPen pen = new(new PenOptions(color, thickness, [3f, 3f]) - { - StrokeOptions = new StrokeOptions { LineCap = LineCap.Butt }, - }); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, true)] - public void DrawLines_EndCapSquare(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) -where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - PatternPen pen = new(new PenOptions(color, thickness, [3f, 3f]) - { - StrokeOptions = new StrokeOptions { LineCap = LineCap.Square }, - }); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 10, true)] - public void DrawLines_JointStyleRound(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) -where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - SolidPen pen = new(new PenOptions(color, thickness) - { - StrokeOptions = new StrokeOptions { LineJoin = LineJoin.Round }, - }); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 10, true)] - public void DrawLines_JointStyleSquare(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) -where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - SolidPen pen = new(new PenOptions(color, thickness) - { - StrokeOptions = new StrokeOptions { LineJoin = LineJoin.Bevel }, - }); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 10, true)] - public void DrawLines_JointStyleMiter(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) -where TPixel : unmanaged, IPixel - { - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - SolidPen pen = new(new PenOptions(color, thickness) - { - StrokeOptions = new StrokeOptions { LineJoin = LineJoin.Miter }, - }); - - DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); - } - - private static void DrawLinesImpl( - TestImageProvider provider, - string colorName, - float alpha, - float thickness, - bool antialias, - Pen pen) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = [new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)]; - - GraphicsOptions options = new() - { Antialias = antialias }; - - string aa = antialias ? string.Empty : "_NoAntialias"; - FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; - - provider.RunValidatingProcessorTest( - c => c.SetGraphicsOptions(options).DrawLine(pen, simplePath), - outputDetails, - appendSourceFileOrDescription: false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawPathTests.cs deleted file mode 100644 index b01b83c11..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawPathTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class DrawPathTests -{ - public static readonly TheoryData DrawPathData = - new() - { - { "White", 255, 1.5f }, - { "Red", 255, 3 }, - { "HotPink", 255, 5 }, - { "HotPink", 150, 5 }, - { "White", 255, 15 }, - }; - - [Theory] - [WithSolidFilledImages(nameof(DrawPathData), 300, 600, "Blue", PixelTypes.Rgba32)] - public void DrawPath(TestImageProvider provider, string colorName, byte alpha, float thickness) - where TPixel : unmanaged, IPixel - { - LinearLineSegment linearSegment = new( - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300)); - CubicBezierLineSegment bezierSegment = new( - new Vector2(50, 300), - new Vector2(500, 500), - new Vector2(60, 10), - new Vector2(10, 400)); - - ArcLineSegment ellipticArcSegment1 = new(new Vector2(10, 400), new Vector2(150, 450), new SizeF((float)Math.Sqrt(5525), 40), GeometryUtilities.RadianToDegree((float)Math.Atan2(25, 70)), true, true); - ArcLineSegment ellipticArcSegment2 = new(new PointF(150, 450), new PointF(149F, 450), new SizeF(140, 70), 0, true, true); - - Path path = new(linearSegment, bezierSegment, ellipticArcSegment1, ellipticArcSegment2); - - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha / 255F); - - FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; - - provider.RunValidatingProcessorTest( - x => x.Draw(color, thickness, path), - testDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(256, 256, "Black", PixelTypes.Rgba32)] - public void PathExtendingOffEdgeOfImageShouldNotBeCropped(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Color color = Color.White; - SolidPen pen = Pens.Solid(color, 5f); - - provider.RunValidatingProcessorTest( - x => - { - for (int i = 0; i < 300; i += 20) - { - PointF[] points = [new Vector2(100, 2), new Vector2(-10, i)]; - x.DrawLine(pen, points); - } - }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(40, 40, "White", PixelTypes.Rgba32)] - public void DrawPathClippedOnTop(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - PointF[] points = - [ - new(10f, -10f), - new(20f, 20f), - new(30f, -30f) - ]; - - IPath path = new PathBuilder().AddLines(points).Build(); - - provider.VerifyOperation( - image => image.Mutate(x => x.Draw(Color.Black, 1, path)), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 360)] - [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 359)] - public void DrawCircleUsingAddArc(TestImageProvider provider, float sweep) - where TPixel : unmanaged, IPixel - { - IPath path = new PathBuilder().AddArc(new Point(150, 150), 50, 50, 0, 40, sweep).Build(); - - provider.VerifyOperation( - image => image.Mutate(x => x.Draw(Color.Black, 1, path)), - testOutputDetails: $"{sweep}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, true)] - [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, false)] - public void DrawCircleUsingArcTo(TestImageProvider provider, bool sweep) - where TPixel : unmanaged, IPixel - { - Point origin = new(150, 150); - IPath path = new PathBuilder().MoveTo(origin).ArcTo(50, 50, 0, true, sweep, origin).Build(); - - provider.VerifyOperation( - image => image.Mutate(x => x.Draw(Color.Black, 1, path)), - testOutputDetails: $"{sweep}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawPolygonTests.cs deleted file mode 100644 index 425bdba81..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawPolygonTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class DrawPolygonTests -{ - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)] - public void DrawPolygon(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - ]; - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - - GraphicsOptions options = new() { Antialias = antialias }; - - string aa = antialias ? string.Empty : "_NoAntialias"; - FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; - - provider.RunValidatingProcessorTest( - c => c.SetGraphicsOptions(options).DrawPolygon(color, thickness, simplePath), - outputDetails, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] - public void DrawPolygon_Transformed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - ]; - - provider.RunValidatingProcessorTest( - c => c.SetDrawingTransform(Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(-15), 0, new Vector2(200, 200))) - .DrawPolygon(Color.White, 2.5f, simplePath)); - } - - [Theory] - [WithBasicTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void DrawRectangularPolygon_Transformed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - RectangularPolygon polygon = new(25, 25, 50, 50); - - provider.RunValidatingProcessorTest( - c => c.SetDrawingTransform(Matrix3x2.CreateRotation((float)Math.PI / 4, new PointF(50, 50))) - .Draw(Color.White, 2.5f, polygon)); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawingProfilingBenchmarks.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawingProfilingBenchmarks.cs deleted file mode 100644 index 00b538d66..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawingProfilingBenchmarks.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using GeoJSON.Net.Feature; -using Newtonsoft.Json; -using SixLabors.Fonts; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -public class DrawingProfilingBenchmarks : IDisposable -{ - private readonly Image image; - private readonly Polygon[] polygons; - - public DrawingProfilingBenchmarks() - { - string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); - - FeatureCollection featureCollection = JsonConvert.DeserializeObject(jsonContent); - - PointF[][] points = GetPoints(featureCollection); - this.polygons = points.Select(pts => new Polygon(new LinearLineSegment(pts))).ToArray(); - - this.image = new Image(1000, 1000); - - static PointF[][] GetPoints(FeatureCollection features) - { - Feature state = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); - - Matrix3x2 transform = Matrix3x2.CreateTranslation(-87, -54) - * Matrix3x2.CreateScale(60, 60); - return PolygonFactory.GetGeoJsonPoints(state, transform).ToArray(); - } - } - - [Theory(Skip = "For local profiling only")] - [InlineData(IntersectionRule.EvenOdd)] - [InlineData(IntersectionRule.NonZero)] - public void FillPolygon(IntersectionRule intersectionRule) - { - const int times = 100; - - for (int i = 0; i < times; i++) - { - this.image.Mutate(c => - { - c.SetShapeOptions(new ShapeOptions() - { - IntersectionRule = intersectionRule - }); - foreach (Polygon polygon in this.polygons) - { - c.Fill(Color.White, polygon); - } - }); - } - } - - [Theory(Skip = "For local profiling only")] - [InlineData(1)] - [InlineData(10)] - public void DrawText(int textIterations) - { - const int times = 20; - const string textPhrase = "asdfghjkl123456789{}[]+$%?"; - string textToRender = string.Join("/n", Enumerable.Repeat(textPhrase, textIterations)); - - Font font = SystemFonts.CreateFont("Arial", 12); - SolidBrush brush = Brushes.Solid(Color.HotPink); - RichTextOptions textOptions = new(font) - { - WrappingLength = 780, - Origin = new PointF(10, 10) - }; - - for (int i = 0; i < times; i++) - { - this.image.Mutate(x => x - .SetGraphicsOptions(o => o.Antialias = true) - .DrawText( - textOptions, - textToRender, - brush)); - } - } - - public void Dispose() => this.image.Dispose(); -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/DrawingRobustnessTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/DrawingRobustnessTests.cs deleted file mode 100644 index 6d230ee2c..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/DrawingRobustnessTests.cs +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#pragma warning disable xUnit1004 // Test methods should not be skipped -using System.Numerics; -using System.Runtime.InteropServices; -using GeoJSON.Net.Feature; -using Newtonsoft.Json; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SkiaSharp; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class DrawingRobustnessTests -{ - [Theory(Skip = "For local testing")] - [WithSolidFilledImages(32, 32, "Black", PixelTypes.Rgba32)] - public void CompareToSkiaResults_SmallCircle(TestImageProvider provider) - { - EllipsePolygon circle = new(16, 16, 10); - - CompareToSkiaResultsImpl(provider, circle); - } - - [Theory(Skip = "For local testing")] - [WithSolidFilledImages(64, 64, "Black", PixelTypes.Rgba32)] - public void CompareToSkiaResults_StarCircle(TestImageProvider provider) - { - EllipsePolygon circle = new(32, 32, 30); - Star star = new(32, 32, 7, 10, 27); - IPath shape = circle.Clip(star); - - CompareToSkiaResultsImpl(provider, shape); - } - - private static void CompareToSkiaResultsImpl(TestImageProvider provider, IPath shape) - { - using Image image = provider.GetImage(); - image.Mutate(c => c.Fill(Color.White, shape)); - image.DebugSave(provider, "ImageSharp", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - - using SKBitmap bitmap = new(new SKImageInfo(image.Width, image.Height)); - - using SKPath skPath = new(); - - foreach (ISimplePath loop in shape.Flatten()) - { - ReadOnlySpan points = MemoryMarshal.Cast(loop.Points.Span); - skPath.AddPoly(points.ToArray()); - } - - using SKPaint paint = new() - { - Style = SKPaintStyle.Fill, - Color = SKColors.White, - IsAntialias = true, - }; - - using SKCanvas canvas = new(bitmap); - canvas.Clear(new SKColor(0, 0, 0)); - canvas.DrawPath(skPath, paint); - - using Image skResultImage = - Image.LoadPixelData(bitmap.GetPixelSpan(), image.Width, image.Height); - skResultImage.DebugSave( - provider, - "SkiaSharp", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - ImageSimilarityReport result = ImageComparer.Exact.CompareImagesOrFrames(image, skResultImage); - throw new ImagesSimilarityException(result.DifferencePercentageString); - } - - [Theory(Skip = "For local testing")] - [WithSolidFilledImages(3600, 2400, "Black", PixelTypes.Rgba32, TestImages.GeoJson.States, 16, 30, 30)] - public void LargeGeoJson_Lines(TestImageProvider provider, string geoJsonFile, int aa, float sx, float sy) - { - string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(geoJsonFile)); - - PointF[][] points = PolygonFactory.GetGeoJsonPoints(jsonContent, Matrix3x2.CreateScale(sx, sy)); - - using Image image = provider.GetImage(); - DrawingOptions options = new() - { - GraphicsOptions = new GraphicsOptions() { Antialias = aa > 0 }, - }; - foreach (PointF[] loop in points) - { - image.Mutate(c => c.DrawLine(options, Color.White, 1.0f, loop)); - } - - string details = $"_{System.IO.Path.GetFileName(geoJsonFile)}_{sx}x{sy}_aa{aa}"; - - image.DebugSave( - provider, - details, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(7200, 3300, "Black", PixelTypes.Rgba32)] - public void LargeGeoJson_States_Fill(TestImageProvider provider) - { - using Image image = FillGeoJsonPolygons(provider, TestImages.GeoJson.States, true, new Vector2(60), new Vector2(0, -1000)); - ImageComparer comparer = ImageComparer.TolerantPercentage(0.001f); - - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - image.CompareToReferenceOutput(comparer, provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - } - - private static Image FillGeoJsonPolygons(TestImageProvider provider, string geoJsonFile, bool aa, Vector2 scale, Vector2 pixelOffset) - { - string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(geoJsonFile)); - - PointF[][] points = PolygonFactory.GetGeoJsonPoints(jsonContent, Matrix3x2.CreateScale(scale) * Matrix3x2.CreateTranslation(pixelOffset)); - - Image image = provider.GetImage(); - DrawingOptions options = new() - { - GraphicsOptions = new GraphicsOptions() { Antialias = aa }, - }; - Random rnd = new(42); - byte[] rgb = new byte[3]; - foreach (PointF[] loop in points) - { - rnd.NextBytes(rgb); - - Color color = Color.FromPixel(new Rgb24(rgb[0], rgb[1], rgb[2])); - image.Mutate(c => c.FillPolygon(options, color, loop)); - } - - return image; - } - - [Theory] - [WithSolidFilledImages(400, 400, "Black", PixelTypes.Rgba32, 0)] - [WithSolidFilledImages(6000, 6000, "Black", PixelTypes.Rgba32, 5500)] - public void LargeGeoJson_Mississippi_Lines(TestImageProvider provider, int pixelOffset) - { - string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); - - FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); - - Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); - - Matrix3x2 transform = Matrix3x2.CreateTranslation(-87, -54) - * Matrix3x2.CreateScale(60, 60) - * Matrix3x2.CreateTranslation(pixelOffset, pixelOffset); - IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); - - using Image image = provider.GetImage(); - foreach (PointF[] loop in points) - { - image.Mutate(c => c.DrawLine(Color.White, 1.0f, loop)); - } - - // Strict comparer, because the image is sparse: - ImageComparer comparer = ImageComparer.TolerantPercentage(0.0001F); - - string details = $"PixelOffset({pixelOffset})"; - image.DebugSave(provider, details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - image.CompareToReferenceOutput(comparer, provider, testOutputDetails: details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(400 * 3, 400 * 3, "Black", PixelTypes.Rgba32, 3)] - [WithSolidFilledImages(400 * 5, 400 * 5, "Black", PixelTypes.Rgba32, 5)] - [WithSolidFilledImages(400 * 10, 400 * 10, "Black", PixelTypes.Rgba32, 10)] - public void LargeGeoJson_Mississippi_LinesScaled(TestImageProvider provider, int scale) - { - string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); - - FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); - - Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); - - Matrix3x2 transform = Matrix3x2.CreateTranslation(-87, -54) - * Matrix3x2.CreateScale(60, 60); - IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); - - using Image image = provider.GetImage(); - var pen = new SolidPen(new SolidBrush(Color.White), 1.0f); - foreach (PointF[] loop in points) - { - IPath outline = pen.GeneratePath(new Path(loop).Transform(Matrix3x2.CreateTranslation(0.5F, 0.5F))); - outline = outline.Transform(Matrix3x2.CreateScale(scale, scale)); - image.Mutate(c => c.Fill(pen.StrokeFill, outline)); - } - - // Strict comparer, because the image is sparse: - ImageComparer comparer = ImageComparer.TolerantPercentage(0.0001F); - - string details = $"Scale({scale})"; - image.DebugSave(provider, details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - image.CompareToReferenceOutput(comparer, provider, testOutputDetails: details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - } - - [Theory(Skip = "For local experiments only")] - [InlineData(0)] - [InlineData(5000)] - [InlineData(9000)] - public void Missisippi_Skia(int offset) - { - string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); - - FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); - - Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); - - Matrix3x2 transform = Matrix3x2.CreateTranslation(-87, -54) - * Matrix3x2.CreateScale(60, 60) - * Matrix3x2.CreateTranslation(offset, offset); - IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); - - SKPath path = new(); - - foreach (PointF[] pts in points.Where(p => p.Length > 2)) - { - path.MoveTo(pts[0].X, pts[0].Y); - - for (int i = 0; i < pts.Length; i++) - { - path.LineTo(pts[i].X, pts[i].Y); - } - - path.LineTo(pts[0].X, pts[0].Y); - } - - SKImageInfo imageInfo = new(10000, 10000); - - using SKPaint paint = new() - { - Style = SKPaintStyle.Stroke, - Color = SKColors.White, - StrokeWidth = 1f, - IsAntialias = true, - }; - - using SKSurface surface = SKSurface.Create(imageInfo); - SKCanvas canvas = surface.Canvas; - canvas.Clear(new SKColor(0, 0, 0)); - canvas.DrawPath(path, paint); - - string outDir = TestEnvironment.CreateOutputDirectory("Skia"); - string fn = System.IO.Path.Combine(outDir, $"Missisippi_Skia_{offset}.png"); - - using SKImage image = surface.Snapshot(); - using SKData data = image.Encode(SKEncodedImageFormat.Png, 100); - - using FileStream fs = File.Create(fn); - data.SaveTo(fs); - } - - [Theory(Skip = "For local experiments only")] - [WithSolidFilledImages(1000, 1000, "Black", PixelTypes.Rgba32, 10)] - public void LargeGeoJson_States_Separate_Benchmark(TestImageProvider provider, int thickness) - { - string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); - - FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); - - Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); - - Matrix3x2 transform = Matrix3x2.CreateTranslation(-87, -54) * Matrix3x2.CreateScale(60, 60); - IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); - - using Image image = provider.GetImage(); - - image.Mutate( - c => - { - foreach (PointF[] loop in points) - { - c.DrawPolygon(Color.White, thickness, loop); - } - }); - - image.DebugSave(provider, $"Benchmark_{thickness}", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - } - - [Theory(Skip = "For local experiments only")] - [WithSolidFilledImages(1000, 1000, "Black", PixelTypes.Rgba32, 10)] - public void LargeGeoJson_States_All_Benchmark(TestImageProvider provider, int thickness) - { - string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); - - FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); - - Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); - - Matrix3x2 transform = Matrix3x2.CreateTranslation(-87, -54) * Matrix3x2.CreateScale(60, 60); - IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); - - PathBuilder pb = new(); - foreach (PointF[] loop in points) - { - pb.StartFigure(); - pb.AddLines(loop); - pb.CloseFigure(); - } - - IPath path = pb.Build(); - - using Image image = provider.GetImage(); - - image.Mutate(c => - { - c.SetRasterizer(DefaultRasterizer.Instance); - c.Draw(Color.White, thickness, path); - }); - - image.DebugSave(provider, $"Benchmark_{thickness}", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - } - - [Theory(Skip = "For local experiments only")] - [WithSolidFilledImages(1000, 1000, "Black", PixelTypes.Rgba32, 10)] - public void LargeStar_Benchmark(TestImageProvider provider, int thickness) - { - List points = CreateStarPolygon(1001, 100F); - Matrix3x2 transform = Matrix3x2.CreateTranslation(250, 250); - - using Image image = provider.GetImage(); - - image.Mutate( - c => - { - foreach (PointF[] loop in points) - { - c.SetDrawingTransform(transform); - c.DrawPolygon(Color.White, thickness, loop); - } - }); - - image.DebugSave(provider, $"Benchmark_{thickness}", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - } - - private static List CreateStarPolygon(int vertexCount, float radius) - { - if (vertexCount < 5 || (vertexCount & 1) == 0) - { - throw new ArgumentOutOfRangeException(nameof(vertexCount), "Vertex count must be an odd number >= 5."); - } - - int step = (vertexCount - 1) / 2; - List contour = new(vertexCount + 1); - for (int i = 0; i < vertexCount; i++) - { - int index = (i * step) % vertexCount; - float angle = (index * MathF.PI * 2) / vertexCount; - contour.Add(new PointF(MathF.Cos(angle) * radius, MathF.Sin(angle) * radius)); - } - - contour.Add(contour[0]); - return [[.. contour]]; - } -} -#pragma warning restore xUnit1004 // Test methods should not be skipped diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillComplexPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillComplexPolygonTests.cs deleted file mode 100644 index 81d77075f..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillComplexPolygonTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class FillComplexPolygonTests -{ - [Theory] - [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, false)] - [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, true, false)] - [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, true)] - public void ComplexPolygon_SolidFill(TestImageProvider provider, bool overlap, bool transparent) - where TPixel : unmanaged, IPixel - { - Polygon simplePath = new(new LinearLineSegment( - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300))); - - Polygon hole1 = new(new LinearLineSegment( - new Vector2(37, 85), - overlap ? new Vector2(130, 40) : new Vector2(93, 85), - new Vector2(65, 137))); - - IPath clipped = simplePath.Clip(hole1); - - Color color = Color.HotPink; - if (transparent) - { - color = color.WithAlpha(150 / 255F); - } - - string testDetails = string.Empty; - if (overlap) - { - testDetails += "_Overlap"; - } - - if (transparent) - { - testDetails += "_Transparent"; - } - - provider.RunValidatingProcessorTest( - x => x.Fill(color, clipped), - testDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillEllipticGradientBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillEllipticGradientBrushTests.cs deleted file mode 100644 index 0bedd4d3f..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillEllipticGradientBrushTests.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing/GradientBrushes")] -public class FillEllipticGradientBrushTests -{ - private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImage(10, 10, PixelTypes.Rgba32)] - public void WithEqualColorsReturnsUnicolorImage( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Color red = Color.Red; - - using (Image image = provider.GetImage()) - { - EllipticGradientBrush unicolorLinearGradientBrush = - new( - new Point(0, 0), - new Point(10, 0), - 1.0f, - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - - // no need for reference image in this test: - image.ComparePixelBufferTo(red); - } - } - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.2)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.6)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 2.0)] - public void AxisParallelEllipsesWithDifferentRatio( - TestImageProvider provider, - float ratio) - where TPixel : unmanaged, IPixel - { - Color yellow = Color.Yellow; - Color red = Color.Red; - Color black = Color.Black; - - provider.VerifyOperation( - TolerantComparer, - image => - { - EllipticGradientBrush unicolorLinearGradientBrush = new( - new Point(image.Width / 2, image.Height / 2), - new Point(image.Width / 2, image.Width * 2 / 3), - ratio, - GradientRepetitionMode.None, - new ColorStop(0, yellow), - new ColorStop(1, red), - new ColorStop(1, black)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - $"{ratio:F2}", - false, - false); - } - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1, 0)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4, 0)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8, 0)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0, 0)] - - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1, 45)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4, 45)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8, 45)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0, 45)] - - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1, 90)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4, 90)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8, 90)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0, 90)] - - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1, 30)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4, 30)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8, 30)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0, 30)] - public void RotatedEllipsesWithDifferentRatio( - TestImageProvider provider, - float ratio, - float rotationInDegree) - where TPixel : unmanaged, IPixel - { - FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg"; - - provider.VerifyOperation( - TolerantComparer, - image => - { - Color yellow = Color.Yellow; - Color red = Color.Red; - Color black = Color.Black; - - Point center = new(image.Width / 2, image.Height / 2); - - double rotation = Math.PI * rotationInDegree / 180.0; - double cos = Math.Cos(rotation); - double sin = Math.Sin(rotation); - - int offsetY = image.Height / 6; - int axisX = center.X + (int)-(offsetY * sin); - int axisY = center.Y + (int)(offsetY * cos); - - EllipticGradientBrush unicolorLinearGradientBrush = new( - center, - new Point(axisX, axisY), - ratio, - GradientRepetitionMode.None, - new ColorStop(0, yellow), - new ColorStop(1, red), - new ColorStop(1, black)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - variant, - false, - false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillImageBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillImageBrushTests.cs deleted file mode 100644 index 7afd37a42..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillImageBrushTests.cs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class FillImageBrushTests -{ - [Fact] - public void DoesNotDisposeImage() - { - using (Image src = new(5, 5)) - { - ImageBrush brush = new(src); - using (Image dest = new(10, 10)) - { - dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); - dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); - } - } - } - - [Theory] - [WithTestPatternImage(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void UseBrushOfDifferentPixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; - using Image background = provider.GetImage(); - using Image overlay = provider.PixelType == PixelTypes.Rgba32 - ? Image.Load(data) - : Image.Load(data); - - ImageBrush brush = new(overlay); - background.Mutate(c => c.Fill(brush)); - - background.DebugSave(provider, appendSourceFileOrDescription: false); - background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); - } - - [Theory] - [WithTestPatternImage(200, 200, PixelTypes.Rgba32)] - public void CanDrawLandscapeImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; - using Image background = provider.GetImage(); - using Image overlay = Image.Load(data); - - overlay.Mutate(c => c.Crop(new Rectangle(0, 0, 125, 90))); - - ImageBrush brush = new(overlay); - background.Mutate(c => c.Fill(brush)); - - background.DebugSave(provider, appendSourceFileOrDescription: false); - background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); - } - - [Theory] - [WithTestPatternImage(200, 200, PixelTypes.Rgba32)] - public void CanDrawPortraitImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; - using Image background = provider.GetImage(); - using Image overlay = Image.Load(data); - - overlay.Mutate(c => c.Crop(new Rectangle(0, 0, 90, 125))); - - ImageBrush brush = new(overlay); - background.Mutate(c => c.Fill(brush)); - - background.DebugSave(provider, appendSourceFileOrDescription: false); - background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); - } - - [Theory] - [WithTestPatternImage(400, 400, PixelTypes.Rgba32)] - public void CanOffsetImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; - using Image background = provider.GetImage(); - using Image overlay = Image.Load(data); - - ImageBrush brush = new(overlay); - background.Mutate(c => c.Fill(brush, new RectangularPolygon(0, 0, 400, 200))); - background.Mutate(c => c.Fill(brush, new RectangularPolygon(-100, 200, 500, 200))); - - background.DebugSave(provider, appendSourceFileOrDescription: false); - background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); - } - - [Theory] - [WithTestPatternImage(400, 400, PixelTypes.Rgba32)] - public void CanOffsetViaBrushImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; - using Image background = provider.GetImage(); - using Image overlay = Image.Load(data); - - ImageBrush brush = new(overlay); - ImageBrush brushOffset = new(overlay, new Point(100, 0)); - background.Mutate(c => c.Fill(brush, new RectangularPolygon(0, 0, 400, 200))); - background.Mutate(c => c.Fill(brushOffset, new RectangularPolygon(0, 200, 400, 200))); - - background.DebugSave(provider, appendSourceFileOrDescription: false); - background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(1000, 1000, "White", PixelTypes.Rgba32)] - public void CanDrawOffsetImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; - using Image background = provider.GetImage(); - - using Image templateImage = Image.Load(data); - using Image finalTexture = BuildMultiRowTexture(templateImage); - - finalTexture.Mutate(c => c.Resize(100, 200)); - - ImageBrush brush = new(finalTexture); - background.Mutate(c => c.Fill(brush)); - - background.DebugSave(provider, appendSourceFileOrDescription: false); - background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); - - Image BuildMultiRowTexture(Image sourceTexture) - { - int halfWidth = sourceTexture.Width / 2; - - Image final = sourceTexture.Clone(x => x.Resize(new ResizeOptions - { - Size = new Size(templateImage.Width, templateImage.Height * 2), - Position = AnchorPositionMode.TopLeft, - Mode = ResizeMode.Pad, - }) - .DrawImage(templateImage, new Point(halfWidth, sourceTexture.Height), new Rectangle(0, 0, halfWidth, sourceTexture.Height), 1) - .DrawImage(templateImage, new Point(0, templateImage.Height), new Rectangle(halfWidth, 0, halfWidth, sourceTexture.Height), 1)); - return final; - } - } - - [Theory] - [WithSolidFilledImages(1000, 1000, "White", PixelTypes.Rgba32)] - public void CanDrawNegativeOffsetImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; - using Image background = provider.GetImage(); - using Image overlay = Image.Load(data); - - overlay.Mutate(c => c.Resize(100, 100)); - - ImageBrush halfBrush = new(overlay, new RectangleF(50, 0, 50, 100)); - ImageBrush fullBrush = new(overlay); - background.Mutate(c => DrawFull(c, new Size(100, 100), fullBrush, halfBrush, background.Width, background.Height)); - - background.DebugSave(provider, appendSourceFileOrDescription: false); - background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); - } - - private static void DrawFull(IImageProcessingContext ctx, Size size, ImageBrush brush, ImageBrush halfBrush, int width, int height) - { - int j = 0; - while (j < height) - { - bool half = false; - int limitWidth = width; - int i = 0; - if ((j / size.Height) % 2 != 0) - { - half = true; - } - - while (i < limitWidth) - { - if (half) - { - ctx.Fill(halfBrush, new RectangleF(i, j, size.Width / 2f, size.Height)); - i += (int)(size.Width / 2f); - half = false; - } - else - { - ctx.Fill(brush, new RectangleF(new PointF(i, j), size)); - i += size.Width; - } - } - - j += size.Height; - } - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillLinearGradientBrushTests.cs deleted file mode 100644 index 9b823400e..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillLinearGradientBrushTests.cs +++ /dev/null @@ -1,463 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Globalization; -using System.Text; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing/GradientBrushes")] -public class FillLinearGradientBrushTests -{ - public static ImageComparer TolerantComparer { get; } = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImage(10, 10, PixelTypes.Rgba32)] - public void WithEqualColorsReturnsUnicolorImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color red = Color.Red; - - LinearGradientBrush unicolorLinearGradientBrush = new( - new Point(0, 0), - new Point(10, 0), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - - // no need for reference image in this test: - image.ComparePixelBufferTo(red); - } - } - - [Theory] - [WithBlankImage(20, 10, PixelTypes.Rgba32)] - [WithBlankImage(20, 10, PixelTypes.Argb32)] - [WithBlankImage(20, 10, PixelTypes.Rgb24)] - public void DoesNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - LinearGradientBrush unicolorLinearGradientBrush = new( - new Point(0, 0), - new Point(image.Width, 0), - GradientRepetitionMode.None, - new ColorStop(0, Color.Blue), - new ColorStop(1, Color.Yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - appendSourceFileOrDescription: false); - - [Theory] - [WithBlankImage(500, 10, PixelTypes.Rgba32)] - public void HorizontalReturnsUnicolorColumns(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - Color red = Color.Red; - Color yellow = Color.Yellow; - - LinearGradientBrush unicolorLinearGradientBrush = new( - new Point(0, 0), - new Point(image.Width, 0), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - false, - false); - - [Theory] - [WithBlankImage(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.DontFill)] - [WithBlankImage(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.None)] - [WithBlankImage(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Repeat)] - [WithBlankImage(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Reflect)] - public void HorizontalGradientWithRepMode( - TestImageProvider provider, - GradientRepetitionMode repetitionMode) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - Color red = Color.Red; - Color yellow = Color.Yellow; - - LinearGradientBrush unicolorLinearGradientBrush = new( - new Point(0, 0), - new Point(image.Width / 10, 0), - repetitionMode, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - $"{repetitionMode}", - false, - false); - - [Theory] - [WithBlankImage(200, 100, PixelTypes.Rgba32, new[] { 0.5f })] - [WithBlankImage(200, 100, PixelTypes.Rgba32, new[] { 0.2f, 0.4f, 0.6f, 0.8f })] - [WithBlankImage(200, 100, PixelTypes.Rgba32, new[] { 0.1f, 0.3f, 0.6f })] - public void WithDoubledStopsProduceDashedPatterns( - TestImageProvider provider, - float[] pattern) - where TPixel : unmanaged, IPixel - { - string variant = string.Join("_", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture))); - - // ensure the input data is valid - Assert.True(pattern.Length > 0); - - Color black = Color.Black; - Color white = Color.White; - - // create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white. - ColorStop[] colorStops = - Enumerable.Repeat(new ColorStop(0, black), 1) - .Concat( - pattern.SelectMany( - (f, index) => - new[] - { - new ColorStop(f, index % 2 == 0 ? black : white), - new ColorStop(f, index % 2 == 0 ? white : black) - })) - .Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? black : white), 1)) - .ToArray(); - - using (Image image = provider.GetImage()) - { - LinearGradientBrush unicolorLinearGradientBrush = - new( - new Point(0, 0), - new Point(image.Width, 0), - GradientRepetitionMode.None, - colorStops); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - - image.DebugSave( - provider, - variant, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - // the result must be a black and white pattern, no other color should occur: - Assert.All( - Enumerable.Range(0, image.Width).Select(i => image[i, 0]), - color => Assert.True( - color.Equals(black.ToPixel()) || color.Equals(white.ToPixel()))); - - image.CompareToReferenceOutput( - TolerantComparer, - provider, - variant, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - } - - [Theory] - [WithBlankImage(10, 500, PixelTypes.Rgba32)] - public void VerticalBrushReturnsUnicolorRows( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.VerifyOperation( - image => - { - Color red = Color.Red; - Color yellow = Color.Yellow; - - LinearGradientBrush unicolorLinearGradientBrush = new( - new Point(0, 0), - new Point(0, image.Height), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - - VerifyAllRowsAreUnicolor(image); - }, - false, - false); - - static void VerifyAllRowsAreUnicolor(Image image) - { - for (int y = 0; y < image.Height; y++) - { - Span row = image.GetRootFramePixelBuffer().DangerousGetRowSpan(y); - TPixel firstColorOfRow = row[0]; - foreach (TPixel p in row) - { - Assert.Equal(firstColorOfRow, p); - } - } - } - } - - public enum ImageCorner - { - TopLeft = 0, - TopRight = 1, - BottomLeft = 2, - BottomRight = 3 - } - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32, ImageCorner.TopLeft)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, ImageCorner.TopRight)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, ImageCorner.BottomLeft)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, ImageCorner.BottomRight)] - public void DiagonalReturnsCorrectImages( - TestImageProvider provider, - ImageCorner startCorner) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Assert.True(image.Height == image.Width, "For the math check block at the end the image must be squared, but it is not."); - - int startX = (int)startCorner % 2 == 0 ? 0 : image.Width - 1; - int startY = startCorner > ImageCorner.TopRight ? 0 : image.Height - 1; - int endX = image.Height - startX - 1; - int endY = image.Width - startY - 1; - - Color red = Color.Red; - Color yellow = Color.Yellow; - - LinearGradientBrush unicolorLinearGradientBrush = - new( - new Point(startX, startY), - new Point(endX, endY), - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - image.DebugSave( - provider, - startCorner, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - int verticalSign = startY == 0 ? 1 : -1; - int horizontalSign = startX == 0 ? 1 : -1; - - for (int i = 0; i < image.Height; i++) - { - // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) - TPixel colorOnDiagonal = image[i, i]; - - // TODO: This is incorrect. from -0 to < 0 ?? - int orthoCount = 0; - for (int offset = -orthoCount; offset < orthoCount; offset++) - { - Assert.Equal(colorOnDiagonal, image[i + (horizontalSign * offset), i + (verticalSign * offset)]); - } - } - - image.CompareToReferenceOutput( - TolerantComparer, - provider, - startCorner, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - } - - [Theory] - [WithBlankImage(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] - [WithBlankImage(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] - [WithBlankImage(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f }, new[] { 0, 1, 2, 0 })] - [WithBlankImage(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f }, new[] { 0, 1, 3 })] - public void ArbitraryGradients( - TestImageProvider provider, - int startX, - int startY, - int endX, - int endY, - float[] stopPositions, - int[] stopColorCodes) - where TPixel : unmanaged, IPixel - { - Color[] colors = - [ - Color.Navy, Color.LightGreen, Color.Yellow, - Color.Red - ]; - - StringBuilder coloringVariant = new(); - ColorStop[] colorStops = new ColorStop[stopPositions.Length]; - - for (int i = 0; i < stopPositions.Length; i++) - { - Color color = colors[stopColorCodes[i % colors.Length]]; - float position = stopPositions[i]; - colorStops[i] = new ColorStop(position, color); - coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color.ToPixel().ToHex(), position); - } - - FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; - - provider.VerifyOperation( - image => - { - LinearGradientBrush unicolorLinearGradientBrush = new( - new Point(startX, startY), - new Point(endX, endY), - GradientRepetitionMode.None, - colorStops); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - variant, - false, - false); - } - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0, 0, 199, 199, new[] { 0f, .25f, .5f, .75f, 1f }, new[] { 0, 1, 2, 3, 4 })] - public void MultiplePointGradients( - TestImageProvider provider, - int startX, - int startY, - int endX, - int endY, - float[] stopPositions, - int[] stopColorCodes) - where TPixel : unmanaged, IPixel - { - Color[] colors = - [ - Color.Black, Color.Blue, Color.Red, - Color.White, Color.Lime - ]; - - StringBuilder coloringVariant = new(); - ColorStop[] colorStops = new ColorStop[stopPositions.Length]; - - for (int i = 0; i < stopPositions.Length; i++) - { - Color color = colors[stopColorCodes[i % colors.Length]]; - float position = stopPositions[i]; - colorStops[i] = new ColorStop(position, color); - coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color.ToPixel().ToHex(), position); - } - - FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; - - provider.VerifyOperation( - image => - { - LinearGradientBrush unicolorLinearGradientBrush = new( - new Point(startX, startY), - new Point(endX, endY), - GradientRepetitionMode.None, - colorStops); - - image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); - }, - variant, - false, - false); - } - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32)] - public void GradientsWithTransparencyOnExistingBackground(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.VerifyOperation( - image => - { - image.Mutate(i => i.Fill(Color.Red)); - image.Mutate(ApplyGloss); - }); - - void ApplyGloss(IImageProcessingContext ctx) - { - Size size = ctx.GetCurrentSize(); - IPathCollection glossPath = BuildGloss(size.Width, size.Height); - GraphicsOptions graphicsOptions = new() - { - Antialias = true, - ColorBlendingMode = PixelColorBlendingMode.Normal, - AlphaCompositionMode = PixelAlphaCompositionMode.SrcAtop - }; - LinearGradientBrush linearGradientBrush = new(new Point(0, 0), new Point(0, size.Height / 2), GradientRepetitionMode.Repeat, new ColorStop(0, Color.White.WithAlpha(0.5f)), new ColorStop(1, Color.White.WithAlpha(0.25f))); - ctx.SetGraphicsOptions(graphicsOptions).Fill(linearGradientBrush, glossPath); - } - - IPathCollection BuildGloss(int imageWidth, int imageHeight) - { - PathBuilder pathBuilder = new(); - pathBuilder.AddLine(new PointF(0, 0), new PointF(imageWidth, 0)); - pathBuilder.AddLine(new PointF(imageWidth, 0), new PointF(imageWidth, imageHeight * 0.4f)); - pathBuilder.AddQuadraticBezier(new PointF(imageWidth, imageHeight * 0.4f), new PointF(imageWidth / 2, imageHeight * 0.6f), new PointF(0, imageHeight * 0.4f)); - pathBuilder.CloseFigure(); - return new PathCollection(pathBuilder.Build()); - } - } - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgb24)] - public void BrushApplicatorIsThreadSafeIssue1044(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - img => - { - PathGradientBrush brush = new( - [new PointF(0, 0), new PointF(200, 0), new PointF(200, 200), new PointF(0, 200), new PointF(0, 0)], - [Color.Red, Color.Yellow, Color.Green, Color.DarkCyan, Color.Red]); - - img.Mutate(m => m.Fill(brush)); - }, - false, - false); - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32)] - public void RotatedGradient(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - image => - { - Color red = Color.Red; - Color yellow = Color.Yellow; - - // Start -> End along TL->BR, rotated to horizontal via p2 - LinearGradientBrush brush = new( - new Point(0, 0), - new Point(200, 200), - new Point(0, 100), // p2 picks horizontal axis - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, yellow)); - image.Mutate(x => x.Fill(brush)); - }, - false, - false); -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillOutsideBoundsTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillOutsideBoundsTests.cs deleted file mode 100644 index bdbd43e06..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillOutsideBoundsTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class FillOutsideBoundsTests -{ - [Theory] - [InlineData(-100)] // Crash - [InlineData(-99)] // Fine - [InlineData(99)] // Fine - [InlineData(100)] // Crash - public void DrawRectactangleOutsideBoundsDrawingArea(int xpos) - { - int width = 100; - int height = 100; - - using (Image image = new(width, height, Color.Red.ToPixel())) - { - Rectangle rectangle = new(xpos, 0, width, height); - - image.Mutate(x => x.Fill(Color.Black, rectangle)); - } - } - - public static TheoryData CircleCoordinates { get; } = new() - { - { -110, -60 }, { 0, -60 }, { 110, -60 }, - { -110, -50 }, { 0, -50 }, { 110, -50 }, - { -110, -49 }, { 0, -49 }, { 110, -49 }, - { -110, -20 }, { 0, -20 }, { 110, -20 }, - { -110, -50 }, { 0, -60 }, { 110, -60 }, - { -110, 0 }, { -99, 0 }, { 0, 0 }, { 99, 0 }, { 110, 0 }, - }; - - [Theory] - [WithSolidFilledImages(nameof(CircleCoordinates), 100, 100, nameof(Color.Red), PixelTypes.Rgba32)] - public void DrawCircleOutsideBoundsDrawingArea(TestImageProvider provider, int xpos, int ypos) - { - int width = 100; - int height = 100; - - using Image image = provider.GetImage(); - EllipsePolygon circle = new(xpos, ypos, width, height); - - provider.RunValidatingProcessorTest( - x => x.Fill(Color.Black, circle), - $"({xpos}_{ypos})", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillPathGradientBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillPathGradientBrushTests.cs deleted file mode 100644 index 15750e4ce..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillPathGradientBrushTests.cs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing/GradientBrushes")] -public class FillPathGradientBrushTests -{ - private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImage(10, 10, PixelTypes.Rgba32)] - public void FillRectangleWithDifferentColors(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; - Color[] colors = [Color.Black, Color.Red, Color.Yellow, Color.Green]; - - PathGradientBrush brush = new(points, colors); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - - [Theory] - [WithBlankImage(20, 20, PixelTypes.Rgba32)] - public void FillTriangleWithDifferentColors(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = [new(10, 0), new(20, 20), new(0, 20)]; - Color[] colors = [Color.Red, Color.Green, Color.Blue]; - - PathGradientBrush brush = new(points, colors); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - - [Theory] - [WithBlankImage(20, 20, PixelTypes.HalfSingle)] - public void FillTriangleWithGreyscale(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - ImageComparer.TolerantPercentage(0.02f), - image => - { - PointF[] points = [new(10, 0), new(20, 20), new(0, 20)]; - - Color c1 = Color.FromPixel(new HalfSingle(-1)); - Color c2 = Color.FromPixel(new HalfSingle(0)); - Color c3 = Color.FromPixel(new HalfSingle(1)); - - Color[] colors = [c1, c2, c3]; - - PathGradientBrush brush = new(points, colors); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - - [Theory] - [WithBlankImage(20, 20, PixelTypes.Rgba32)] - public void FillTriangleWithDifferentColorsCenter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = [new(10, 0), new(20, 20), new(0, 20)]; - Color[] colors = [Color.Red, Color.Green, Color.Blue]; - - PathGradientBrush brush = new(points, colors, Color.White); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - - [Theory] - [WithBlankImage(10, 10, PixelTypes.Rgba32)] - public void FillRectangleWithSingleColor(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; - Color[] colors = [Color.Red]; - - PathGradientBrush brush = new(points, colors); - - image.Mutate(x => x.Fill(brush)); - - image.ComparePixelBufferTo(Color.Red); - } - } - - [Theory] - [WithBlankImage(10, 10, PixelTypes.Rgba32)] - public void ShouldRotateTheColorsWhenThereAreMorePoints(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; - Color[] colors = [Color.Red, Color.Yellow]; - - PathGradientBrush brush = new(points, colors); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - - [Theory] - [WithBlankImage(10, 10, PixelTypes.Rgba32)] - public void FillWithCustomCenterColor(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; - Color[] colors = [Color.Black, Color.Red, Color.Yellow, Color.Green]; - - PathGradientBrush brush = new(points, colors, Color.White); - - image.Mutate(x => x.Fill(brush)); - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - }); - - [Fact] - public void ShouldThrowArgumentNullExceptionWhenLinesAreNull() - { - Color[] colors = [Color.Black, Color.Red, Color.Yellow, Color.Green]; - - PathGradientBrush Create() => new(null, colors, Color.White); - - Assert.Throws(Create); - } - - [Fact] - public void ShouldThrowArgumentOutOfRangeExceptionWhenLessThan3PointsAreGiven() - { - PointF[] points = [new(0, 0), new(10, 0)]; - Color[] colors = [Color.Black, Color.Red, Color.Yellow, Color.Green]; - - PathGradientBrush Create() => new(points, colors, Color.White); - - Assert.Throws(Create); - } - - [Fact] - public void ShouldThrowArgumentNullExceptionWhenColorsAreNull() - { - PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; - - PathGradientBrush Create() => new(points, null, Color.White); - - Assert.Throws(Create); - } - - [Fact] - public void ShouldThrowArgumentOutOfRangeExceptionWhenEmptyColorArrayIsGiven() - { - PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; - - Color[] colors = []; - - PathGradientBrush Create() => new(points, colors, Color.White); - - Assert.Throws(Create); - } - - [Theory] - [WithBlankImage(100, 100, PixelTypes.Rgba32)] - public void FillComplex(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - new TolerantImageComparer(0.2f), - image => - { - Star star = new(50, 50, 5, 20, 45); - PointF[] points = star.Points.ToArray(); - Color[] colors = - [ - Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Purple, - Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Purple - ]; - - PathGradientBrush brush = new(points, colors, Color.White); - - image.Mutate(x => x.Fill(brush)); - }, - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillPathTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillPathTests.cs deleted file mode 100644 index 1a6bcb5e9..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillPathTests.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class FillPathTests -{ - // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths - [Theory] - [WithSolidFilledImages(325, 325, "White", PixelTypes.Rgba32)] - public void FillPathSVGArcs(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - PathBuilder pb = new(); - - pb.MoveTo(new Vector2(80, 80)) - .ArcTo(45, 45, 0, false, false, new Vector2(125, 125)) - .LineTo(new Vector2(125, 80)) - .CloseFigure(); - - IPath path = pb.Build(); - - pb = new PathBuilder(); - pb.MoveTo(new Vector2(230, 80)) - .ArcTo(45, 45, 0, true, false, new Vector2(275, 125)) - .LineTo(new Vector2(275, 80)) - .CloseFigure(); - - IPath path2 = pb.Build(); - - pb = new PathBuilder(); - pb.MoveTo(new Vector2(80, 230)) - .ArcTo(45, 45, 0, false, true, new Vector2(125, 275)) - .LineTo(new Vector2(125, 230)) - .CloseFigure(); - - IPath path3 = pb.Build(); - - pb = new PathBuilder(); - pb.MoveTo(new Vector2(230, 230)) - .ArcTo(45, 45, 0, true, true, new Vector2(275, 275)) - .LineTo(new Vector2(275, 230)) - .CloseFigure(); - - IPath path4 = pb.Build(); - - provider.VerifyOperation( - image => image.Mutate(x => x.Fill(Color.Green, path).Fill(Color.Red, path2).Fill(Color.Purple, path3).Fill(Color.Blue, path4)), - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); - } - - // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc - [Theory] - [WithSolidFilledImages(150, 200, "White", PixelTypes.Rgba32)] - public void FillPathCanvasArcs(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - ImageComparer.TolerantPercentage(5e-3f), - image => - { - for (int i = 0; i <= 3; i++) - { - for (int j = 0; j <= 2; j++) - { - PathBuilder pb = new(); - - float x = 25 + (j * 50); // x coordinate - float y = 25 + (i * 50); // y coordinate - float radius = 20; // Arc radius - float startAngle = 0; // Starting point on circle - float endAngle = 180F + (180F * j / 2F); // End point on circle - bool counterclockwise = i % 2 == 1; // Draw counterclockwise - - // To move counterclockwise we offset our sweepAngle parameter - // Canvas likely does something similar. - if (counterclockwise) - { - // 360 becomes zero and we don't accept that as a parameter (won't render). - if (endAngle < 360F) - { - endAngle = (360F - endAngle) % 360F; - } - - endAngle *= -1; - } - - pb.AddArc(x, y, radius, radius, 0, startAngle, endAngle); - - if (i > 1) - { - image.Mutate(x => x.Fill(Color.Black, pb.Build())); - } - else - { - image.Mutate(x => x.Draw(new SolidPen(Color.Black, 1), pb.Build())); - } - } - } - }, - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); - - [Theory] - [WithSolidFilledImages(400, 250, "White", PixelTypes.Rgba32)] - public void FillPathArcToAlternates(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Test alternate syntax. Both should overlap creating an orange arc. - PathBuilder pb = new(); - - pb.MoveTo(new Vector2(50, 50)); - pb.ArcTo(20, 50, -72, false, true, new Vector2(200, 200)); - IPath path = pb.Build(); - - pb = new PathBuilder(); - pb.MoveTo(new Vector2(50, 50)); - pb.AddSegment(new ArcLineSegment(new Vector2(50, 50), new Vector2(200, 200), new SizeF(20, 50), -72F, true, true)); - IPath path2 = pb.Build(); - - provider.VerifyOperation( - image => image.Mutate(x => x.Fill(Color.Yellow, path).Fill(Color.Red.WithAlpha(.5F), path2)), - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillPatternBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillPatternBrushTests.cs deleted file mode 100644 index 3fbba995f..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillPatternBrushTests.cs +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -public class FillPatternBrushTests -{ - private void Test(string name, Color background, Brush brush, Color[,] expectedPattern) - { - string path = TestEnvironment.CreateOutputDirectory("Drawing", "FillPatternBrushTests"); - using (Image image = new(20, 20)) - { - image.Mutate(x => x.Fill(background).Fill(brush)); - - image.Save($"{path}/{name}.png"); - - Buffer2D sourcePixels = image.GetRootFramePixelBuffer(); - - // lets pick random spots to start checking - Random r = new(); - DenseMatrix expectedPatternFast = new(expectedPattern); - int xStride = expectedPatternFast.Columns; - int yStride = expectedPatternFast.Rows; - int offsetX = r.Next(image.Width / xStride) * xStride; - int offsetY = r.Next(image.Height / yStride) * yStride; - for (int x = 0; x < xStride; x++) - { - for (int y = 0; y < yStride; y++) - { - int actualX = x + offsetX; - int actualY = y + offsetY; - Rgba32 expected = expectedPatternFast[y, x].ToPixel(); // inverted pattern - Rgba32 actual = sourcePixels[actualX, actualY]; - if (expected != actual) - { - Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})"); - } - } - } - - image.Mutate(x => x.Resize(80, 80, KnownResamplers.NearestNeighbor)); - image.Save($"{path}/{name}x4.png"); - } - } - - [Fact] - public void ImageShouldBeFloodFilledWithPercent10() - { - Color[,] expectedPattern = new Color[,] - { - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen } - }; - - Test( - "Percent10", - Color.Blue, - Brushes.Percent10(Color.HotPink, Color.LimeGreen), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithPercent10Transparent() - { - Color[,] expectedPattern = new Color[,] - { - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue }, - { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, - { Color.Blue, Color.Blue, Color.HotPink, Color.Blue }, - { Color.Blue, Color.Blue, Color.Blue, Color.Blue } - }; - - Test( - "Percent10_Transparent", - Color.Blue, - Brushes.Percent10(Color.HotPink), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithPercent20() - { - Color[,] expectedPattern = new Color[,] - { - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen }, - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen } - }; - - Test( - "Percent20", - Color.Blue, - Brushes.Percent20(Color.HotPink, Color.LimeGreen), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithPercent20_transparent() - { - Color[,] expectedPattern = new Color[,] - { - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue }, - { Color.Blue, Color.Blue, Color.HotPink, Color.Blue }, - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue }, - { Color.Blue, Color.Blue, Color.HotPink, Color.Blue } - }; - - Test( - "Percent20_Transparent", - Color.Blue, - Brushes.Percent20(Color.HotPink), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithHorizontal() - { - Color[,] expectedPattern = new Color[,] - { - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.HotPink, Color.HotPink, Color.HotPink, Color.HotPink }, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen } - }; - - Test( - "Horizontal", - Color.Blue, - Brushes.Horizontal(Color.HotPink, Color.LimeGreen), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithHorizontal_transparent() - { - Color[,] expectedPattern = new Color[,] - { - { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, - { Color.HotPink, Color.HotPink, Color.HotPink, Color.HotPink }, - { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, - { Color.Blue, Color.Blue, Color.Blue, Color.Blue } - }; - - Test( - "Horizontal_Transparent", - Color.Blue, - Brushes.Horizontal(Color.HotPink), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithMin() - { - Color[,] expectedPattern = new Color[,] - { - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.HotPink, Color.HotPink, Color.HotPink, Color.HotPink } - }; - - Test( - "Min", - Color.Blue, - Brushes.Min(Color.HotPink, Color.LimeGreen), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithMin_transparent() - { - Color[,] expectedPattern = new Color[,] - { - { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, - { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, - { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, - { Color.HotPink, Color.HotPink, Color.HotPink, Color.HotPink }, - }; - - Test( - "Min_Transparent", - Color.Blue, - Brushes.Min(Color.HotPink), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithVertical() - { - Color[,] expectedPattern = new Color[,] - { - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen } - }; - - Test( - "Vertical", - Color.Blue, - Brushes.Vertical(Color.HotPink, Color.LimeGreen), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithVertical_transparent() - { - Color[,] expectedPattern = new Color[,] - { - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue } - }; - - Test( - "Vertical_Transparent", - Color.Blue, - Brushes.Vertical(Color.HotPink), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithForwardDiagonal() - { - Color[,] expectedPattern = new Color[,] - { - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink }, - { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen }, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen } - }; - - Test( - "ForwardDiagonal", - Color.Blue, - Brushes.ForwardDiagonal(Color.HotPink, Color.LimeGreen), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithForwardDiagonal_transparent() - { - Color[,] expectedPattern = new Color[,] - { - { Color.Blue, Color.Blue, Color.Blue, Color.HotPink }, - { Color.Blue, Color.Blue, Color.HotPink, Color.Blue }, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue } - }; - - Test( - "ForwardDiagonal_Transparent", - Color.Blue, - Brushes.ForwardDiagonal(Color.HotPink), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithBackwardDiagonal() - { - Color[,] expectedPattern = new Color[,] - { - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen }, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink } - }; - - Test( - "BackwardDiagonal", - Color.Blue, - Brushes.BackwardDiagonal(Color.HotPink, Color.LimeGreen), - expectedPattern); - } - - [Fact] - public void ImageShouldBeFloodFilledWithBackwardDiagonal_transparent() - { - Color[,] expectedPattern = new Color[,] - { - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue }, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, - { Color.Blue, Color.Blue, Color.HotPink, Color.Blue }, - { Color.Blue, Color.Blue, Color.Blue, Color.HotPink } - }; - - Test( - "BackwardDiagonal_Transparent", - Color.Blue, - Brushes.BackwardDiagonal(Color.HotPink), - expectedPattern); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillPolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillPolygonTests.cs deleted file mode 100644 index 3fc6f89fb..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillPolygonTests.cs +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class FillPolygonTests -{ - [Theory] - [WithSolidFilledImages(8, 12, nameof(Color.Black), PixelTypes.Rgba32, 0)] - [WithSolidFilledImages(8, 12, nameof(Color.Black), PixelTypes.Rgba32, 8)] - [WithSolidFilledImages(8, 12, nameof(Color.Black), PixelTypes.Rgba32, 16)] - public void FillPolygon_Solid_Basic(TestImageProvider provider, int antialias) - where TPixel : unmanaged, IPixel - { - PointF[] polygon1 = PolygonFactory.CreatePointArray((2, 2), (6, 2), (6, 4), (2, 4)); - PointF[] polygon2 = PolygonFactory.CreatePointArray((2, 8), (4, 6), (6, 8), (4, 10)); - - GraphicsOptions options = new() { Antialias = antialias > 0 }; - provider.RunValidatingProcessorTest( - c => c.SetGraphicsOptions(options) - .FillPolygon(Color.White, polygon1) - .FillPolygon(Color.White, polygon2), - testOutputDetails: $"aa{antialias}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, true)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, false)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, true)] - public void FillPolygon_Solid(TestImageProvider provider, string colorName, float alpha, bool antialias) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - ]; - Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); - - GraphicsOptions options = new() { Antialias = antialias }; - - string aa = antialias ? string.Empty : "_NoAntialias"; - FormattableString outputDetails = $"{colorName}_A{alpha}{aa}"; - - provider.RunValidatingProcessorTest( - c => c.SetGraphicsOptions(options).FillPolygon(color, simplePath), - outputDetails, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] - public void FillPolygon_Solid_Transformed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - ]; - - provider.RunValidatingProcessorTest( - c => c.SetDrawingTransform(Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(-15), 0, new Vector2(200, 200))) - .FillPolygon(Color.White, simplePath)); - } - - [Theory] - [WithBasicTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void Fill_RectangularPolygon_Solid_Transformed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - RectangularPolygon polygon = new(25, 25, 50, 50); - - provider.RunValidatingProcessorTest( - c => c.SetDrawingTransform(Matrix3x2.CreateRotation((float)Math.PI / 4, new PointF(50, 50))) - .Fill(Color.White, polygon)); - } - - [Theory] - [WithBasicTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void Fill_RectangularPolygon_Solid_TransformedUsingConfiguration(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - RectangularPolygon polygon = new(25, 25, 50, 50); - provider.Configuration.SetDrawingTransform(Matrix3x2.CreateRotation((float)Math.PI / 4, new PointF(50, 50))); - provider.RunValidatingProcessorTest(c => c.Fill(Color.White, polygon)); - } - - public static TheoryData FillPolygon_Complex_Data { get; } = - new() - { - { false, IntersectionRule.EvenOdd }, - { false, IntersectionRule.NonZero }, - { true, IntersectionRule.EvenOdd }, - { true, IntersectionRule.NonZero }, - }; - - [Theory] - [WithBasicTestPatternImages(nameof(FillPolygon_Complex_Data), 100, 100, PixelTypes.Rgba32)] - public void FillPolygon_Complex(TestImageProvider provider, bool reverse, IntersectionRule intersectionRule) - where TPixel : unmanaged, IPixel - { - PointF[] contour = PolygonFactory.CreatePointArray((20, 20), (80, 20), (80, 80), (20, 80)); - PointF[] hole = PolygonFactory.CreatePointArray((40, 40), (40, 60), (60, 60), (60, 40)); - - if (reverse) - { - Array.Reverse(contour); - Array.Reverse(hole); - } - - ComplexPolygon polygon = new( - new Path(new LinearLineSegment(contour)), - new Path(new LinearLineSegment(hole))); - - provider.RunValidatingProcessorTest( - c => - { - c.SetShapeOptions(new ShapeOptions() - { - IntersectionRule = intersectionRule - }); - c.Fill(Color.White, polygon); - }, - testOutputDetails: $"Reverse({reverse})_IntersectionRule({intersectionRule})", - comparer: ImageComparer.TolerantPercentage(0.01f), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, false)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, true)] - public void FillPolygon_Concave(TestImageProvider provider, bool reverse) - where TPixel : unmanaged, IPixel - { - PointF[] points = - [ - new Vector2(8, 8), - new Vector2(64, 8), - new Vector2(64, 64), - new Vector2(120, 64), - new Vector2(120, 120), - new Vector2(8, 120) - ]; - if (reverse) - { - Array.Reverse(points); - } - - Color color = Color.LightGreen; - - provider.RunValidatingProcessorTest( - c => c.FillPolygon(color, points), - testOutputDetails: $"Reverse({reverse})", - comparer: ImageComparer.TolerantPercentage(0.01f), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(64, 64, "Black", PixelTypes.Rgba32)] - public void FillPolygon_StarCircle(TestImageProvider provider) - { - EllipsePolygon circle = new(32, 32, 30); - Star star = new(32, 32, 7, 10, 27); - IPath shape = circle.Clip(star); - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White, shape), - comparer: ImageComparer.TolerantPercentage(0.01f), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(128, 128, "Black", PixelTypes.Rgba32, BooleanOperation.Intersection)] - [WithSolidFilledImages(128, 128, "Black", PixelTypes.Rgba32, BooleanOperation.Union)] - [WithSolidFilledImages(128, 128, "Black", PixelTypes.Rgba32, BooleanOperation.Difference)] - [WithSolidFilledImages(128, 128, "Black", PixelTypes.Rgba32, BooleanOperation.Xor)] - public void FillPolygon_StarCircle_AllOperations(TestImageProvider provider, BooleanOperation operation) - { - IPath circle = new EllipsePolygon(36, 36, 36).Translate(28, 28); - Star star = new(64, 64, 5, 24, 64); - - // See http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/ClipType.htm for reference. - ShapeOptions options = new() { BooleanOperation = operation }; - IPath shape = star.Clip(options, circle); - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.DeepPink, circle).Fill(Color.LightGray, star).Fill(Color.ForestGreen, shape), - testOutputDetails: operation.ToString(), - comparer: ImageComparer.TolerantPercentage(0.01F), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] - public void FillPolygon_Pattern(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - ]; - Color color = Color.Yellow; - - PatternBrush brush = Brushes.Horizontal(color); - - provider.RunValidatingProcessorTest( - c => c.FillPolygon(brush, simplePath), - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)] - public void FillPolygon_ImageBrush(TestImageProvider provider, string brushImageName) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200) - ]; - - using (Image brushImage = Image.Load(TestFile.Create(brushImageName).Bytes)) - { - ImageBrush brush = new(brushImage); - - provider.RunValidatingProcessorTest( - c => c.FillPolygon(brush, simplePath), - System.IO.Path.GetFileNameWithoutExtension(brushImageName), - appendSourceFileOrDescription: false); - } - } - - [Theory] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)] - [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)] - public void FillPolygon_ImageBrush_Rect(TestImageProvider provider, string brushImageName) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200) - ]; - - using (Image brushImage = Image.Load(TestFile.Create(brushImageName).Bytes)) - { - float top = brushImage.Height / 4; - float left = brushImage.Width / 4; - float height = top * 2; - float width = left * 2; - - ImageBrush brush = new(brushImage, new RectangleF(left, top, width, height)); - - provider.RunValidatingProcessorTest( - c => c.FillPolygon(brush, simplePath), - System.IO.Path.GetFileNameWithoutExtension(brushImageName) + "_rect", - appendSourceFileOrDescription: false); - } - } - - [Theory] - [WithBasicTestPatternImages(250, 250, PixelTypes.Rgba32)] - public void Fill_RectangularPolygon(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - RectangularPolygon polygon = new(10, 10, 190, 140); - Color color = Color.White; - - provider.RunValidatingProcessorTest( - c => c.Fill(color, polygon), - appendSourceFileOrDescription: false); - } - - [Theory] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 50, 0f)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, 20f)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, -180f)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 5, 70, 0f)] - [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 7, 80, -180f)] - public void Fill_RegularPolygon(TestImageProvider provider, int vertices, float radius, float angleDeg) - where TPixel : unmanaged, IPixel - { - float angle = GeometryUtilities.DegreeToRadian(angleDeg); - RegularPolygon polygon = new(100, 100, vertices, radius, angle); - Color color = Color.Yellow; - - FormattableString testOutput = $"V({vertices})_R({radius})_Ang({angleDeg})"; - provider.RunValidatingProcessorTest( - c => c.Fill(color, polygon), - testOutput, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - public static readonly TheoryData Fill_EllipsePolygon_Data = - new() - { - { false, IntersectionRule.EvenOdd }, - { false, IntersectionRule.NonZero }, - { true, IntersectionRule.EvenOdd }, - { true, IntersectionRule.NonZero }, - }; - - [Theory] - [WithBasicTestPatternImages(nameof(Fill_EllipsePolygon_Data), 200, 200, PixelTypes.Rgba32)] - public void Fill_EllipsePolygon(TestImageProvider provider, bool reverse, IntersectionRule intersectionRule) - where TPixel : unmanaged, IPixel - { - IPath polygon = new EllipsePolygon(100, 100, 80, 120); - if (reverse) - { - polygon = polygon.Reverse(); - } - - Color color = Color.Azure; - - provider.RunValidatingProcessorTest( - c => - { - c.SetShapeOptions(new ShapeOptions() - { - IntersectionRule = intersectionRule - }); - c.Fill(color, polygon); - }, - testOutputDetails: $"Reverse({reverse})_IntersectionRule({intersectionRule})", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Theory] - [WithSolidFilledImages(60, 60, "Blue", PixelTypes.Rgba32)] - public void Fill_IntersectionRules_OddEven(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image img = provider.GetImage()) - { - Polygon poly = new(new LinearLineSegment( - new PointF(10, 30), - new PointF(10, 20), - new PointF(50, 20), - new PointF(50, 50), - new PointF(20, 50), - new PointF(20, 10), - new PointF(30, 10), - new PointF(30, 40), - new PointF(40, 40), - new PointF(40, 30), - new PointF(10, 30))); - - img.Mutate(c => c.Fill( - new DrawingOptions - { - ShapeOptions = { IntersectionRule = IntersectionRule.EvenOdd }, - }, - Color.HotPink, - poly)); - - provider.Utility.SaveTestOutputFile(img); - - Assert.Equal(Color.Blue.ToPixel(), img[25, 25]); - } - } - - [Theory] - [WithSolidFilledImages(60, 60, "Blue", PixelTypes.Rgba32)] - public void Fill_IntersectionRules_Nonzero(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Configuration.Default.MaxDegreeOfParallelism = 1; - using (Image img = provider.GetImage()) - { - Polygon poly = new(new LinearLineSegment( - new PointF(10, 30), - new PointF(10, 20), - new PointF(50, 20), - new PointF(50, 50), - new PointF(20, 50), - new PointF(20, 10), - new PointF(30, 10), - new PointF(30, 40), - new PointF(40, 40), - new PointF(40, 30), - new PointF(10, 30))); - img.Mutate(c => c.Fill( - new DrawingOptions - { - ShapeOptions = { IntersectionRule = IntersectionRule.NonZero }, - }, - Color.HotPink, - poly)); - - provider.Utility.SaveTestOutputFile(img); - Assert.Equal(Color.HotPink.ToPixel(), img[25, 25]); - } - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillRadialGradientBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillRadialGradientBrushTests.cs deleted file mode 100644 index 3fb5cbf94..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillRadialGradientBrushTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; - -[GroupOutput("Drawing/GradientBrushes")] -public class FillRadialGradientBrushTests -{ - public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32)] - public void WithEqualColorsReturnsUnicolorImage( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color red = Color.Red; - - RadialGradientBrush unicolorRadialGradientBrush = - new( - new Point(0, 0), - 100, - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(1, red)); - - image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); - - image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - - // no need for reference image in this test: - image.ComparePixelBufferTo(red); - } - } - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 100, 100)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0, 0)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 100, 0)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0, 100)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, -40, 100)] - public void WithDifferentCentersReturnsImage( - TestImageProvider provider, - int centerX, - int centerY) - where TPixel : unmanaged, IPixel - { - provider.VerifyOperation( - TolerantComparer, - image => - { - RadialGradientBrush brush = new( - new Point(centerX, centerY), - image.Width / 2f, - GradientRepetitionMode.None, - new ColorStop(0, Color.Red), - new ColorStop(1, Color.Yellow)); - - image.Mutate(x => x.Fill(brush)); - }, - $"center({centerX},{centerY})", - false, - false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillSolidBrushTests.cs deleted file mode 100644 index 8ecbdef49..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillSolidBrushTests.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class FillSolidBrushTests -{ - [Theory] - [WithBlankImage(1, 1, PixelTypes.Rgba32)] - [WithBlankImage(7, 4, PixelTypes.Rgba32)] - [WithBlankImage(16, 7, PixelTypes.Rgba32)] - [WithBlankImage(33, 32, PixelTypes.Rgba32)] - [WithBlankImage(400, 500, PixelTypes.Rgba32)] - public void DoesNotDependOnSize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color color = Color.HotPink; - image.Mutate(c => c.Fill(color)); - - image.DebugSave(provider, appendPixelTypeToFileName: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithBlankImage(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] - public void DoesNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color color = Color.HotPink; - image.Mutate(c => c.Fill(color)); - - image.DebugSave(provider, appendSourceFileOrDescription: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] - [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] - public void WhenColorIsOpaque_OverridePreviousColor( - TestImageProvider provider, - string newColorName) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) - { - Color color = TestUtils.GetColorByName(newColorName); - image.Mutate(c => c.Fill(color)); - - image.DebugSave( - provider, - newColorName, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - image.ComparePixelBufferTo(color); - } - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] - public void FillRegion(TestImageProvider provider, int x0, int y0, int w, int h) - where TPixel : unmanaged, IPixel - { - FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; - RectangleF region = new(x0, y0, w, h); - Color color = TestUtils.GetColorByName("Blue"); - - provider.RunValidatingProcessorTest(c => c.Fill(color, region), testDetails, ImageComparer.Exact); - } - - [Theory] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] - [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] - public void FillRegion_WorksOnWrappedMemoryImage( - TestImageProvider provider, - int x0, - int y0, - int w, - int h) - where TPixel : unmanaged, IPixel - { - FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; - RectangleF region = new(x0, y0, w, h); - Color color = TestUtils.GetColorByName("Blue"); - - provider.RunValidatingProcessorTestOnWrappedMemoryImage( - c => c.Fill(color, region), - testDetails, - ImageComparer.Exact, - useReferenceOutputFrom: nameof(this.FillRegion)); - } - - public static readonly TheoryData BlendData = - new() - { - { false, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, - { false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, - { false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, - { false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, - { false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, - { false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, - { true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, - { true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, - { true, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, - }; - - [Theory] - [WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)] - public void BlendFillColorOverBackground( - TestImageProvider provider, - bool triggerFillRegion, - string newColorName, - float alpha, - PixelColorBlendingMode blenderMode, - float blendPercentage) - where TPixel : unmanaged, IPixel - { - Color fillColor = TestUtils.GetColorByName(newColorName).WithAlpha(alpha); - - using (Image image = provider.GetImage()) - { - TPixel bgColor = image[0, 0]; - - DrawingOptions options = new() - { - GraphicsOptions = new GraphicsOptions - { - Antialias = false, - ColorBlendingMode = blenderMode, - BlendPercentage = blendPercentage - } - }; - - if (triggerFillRegion) - { - RectangularPolygon path = new(0, 0, 16, 16); - image.Mutate(c => c.SetGraphicsOptions(options.GraphicsOptions).Fill(new SolidBrush(fillColor), path)); - } - else - { - image.Mutate(c => c.Fill(options, new SolidBrush(fillColor))); - } - - var testOutputDetails = new - { - triggerFillRegion, - newColorName, - alpha, - blenderMode, - blendPercentage - }; - - image.DebugSave( - provider, - testOutputDetails, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - PixelBlender blender = PixelOperations.Instance.GetPixelBlender( - blenderMode, - PixelAlphaCompositionMode.SrcOver); - TPixel expectedPixel = blender.Blend(bgColor, fillColor.ToPixel(), blendPercentage); - - image.ComparePixelBufferTo(expectedPixel); - } - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/FillSweepGradientBrushTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/FillSweepGradientBrushTests.cs deleted file mode 100644 index cc4518e6d..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/FillSweepGradientBrushTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing/GradientBrushes")] -public class FillSweepGradientBrushTests -{ - private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 0f, 360f)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 90f, 450f)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 180f, 540f)] - [WithBlankImage(200, 200, PixelTypes.Rgba32, 270f, 630f)] - public void SweepGradientBrush_RendersFullSweep_Every90Degrees(TestImageProvider provider, float start, float end) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - TolerantComparer, - image => - { - Color red = Color.Red; - Color green = Color.Green; - Color blue = Color.Blue; - Color yellow = Color.Yellow; - - SweepGradientBrush brush = new( - new Point(100, 100), - start, - end, - GradientRepetitionMode.None, - new ColorStop(0, red), - new ColorStop(0.25F, yellow), - new ColorStop(0.5F, green), - new ColorStop(0.75F, blue), - new ColorStop(1, red)); - - image.Mutate(x => x.Fill(brush)); - }, - $"start({start},end{end})", - false, - false); -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/Clear.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/Clear.cs deleted file mode 100644 index 178dfa48e..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/Clear.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class Clear : BaseImageOperationsExtensionTest -{ - private readonly DrawingOptions nonDefaultOptions = new() - { - GraphicsOptions = - { - AlphaCompositionMode = PixelFormats.PixelAlphaCompositionMode.Clear, - BlendPercentage = 0.5f, - ColorBlendingMode = PixelFormats.PixelColorBlendingMode.Darken - } - }; - - private readonly Brush brush = new SolidBrush(Color.HotPink); - - [Fact] - public void Brush() - { - this.operations.Clear(this.nonDefaultOptions, this.brush); - - FillProcessor processor = this.Verify(); - - DrawingOptions expectedOptions = this.nonDefaultOptions; - Assert.Equal(expectedOptions.ShapeOptions, processor.Options.ShapeOptions); - Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void BrushDefaultOptions() - { - this.operations.Clear(this.brush); - - FillProcessor processor = this.Verify(); - - ShapeOptions expectedOptions = this.shapeOptions; - Assert.Equal(expectedOptions, processor.Options.ShapeOptions); - Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void ColorSet() - { - this.operations.Clear(this.nonDefaultOptions, Color.Red); - - FillProcessor processor = this.Verify(); - - ShapeOptions expectedOptions = this.shapeOptions; - Assert.NotEqual(expectedOptions, processor.Options.ShapeOptions); - - Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void ColorSetDefaultOptions() - { - this.operations.Clear(Color.Red); - - FillProcessor processor = this.Verify(); - - ShapeOptions expectedOptions = this.shapeOptions; - Assert.Equal(expectedOptions, processor.Options.ShapeOptions); - Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearPath.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearPath.cs deleted file mode 100644 index 155602407..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearPath.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class ClearPath : BaseImageOperationsExtensionTest -{ - private readonly DrawingOptions nonDefaultOptions = new() - { - GraphicsOptions = - { - AlphaCompositionMode = PixelFormats.PixelAlphaCompositionMode.Clear, - BlendPercentage = 0.5f, - ColorBlendingMode = PixelFormats.PixelColorBlendingMode.Darken - } - }; - - private readonly Brush brush = Brushes.Solid(Color.HotPink); - private readonly IPath path = new Star(1, 10, 5, 23, 56); - - [Fact] - public void Brush() - { - this.operations.Clear(this.nonDefaultOptions, this.brush, this.path); - - FillPathProcessor processor = this.Verify(); - - ShapeOptions expectedOptions = this.shapeOptions; - Assert.NotEqual(expectedOptions, processor.Options.ShapeOptions); - Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.Equal(this.path, processor.Region); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void BrushDefaultOptions() - { - this.operations.Clear(this.brush, this.path); - - FillPathProcessor processor = this.Verify(); - - ShapeOptions expectedOptions = this.shapeOptions; - Assert.Equal(expectedOptions, processor.Options.ShapeOptions); - Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.Equal(this.path, processor.Region); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void ColorSet() - { - this.operations.Clear(this.nonDefaultOptions, Color.Red, this.path); - - FillPathProcessor processor = this.Verify(); - - ShapeOptions expectedOptions = this.shapeOptions; - Assert.NotEqual(expectedOptions, processor.Options.ShapeOptions); - - Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); - Assert.Equal(this.path, processor.Region); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.Clear(Color.Red, this.path); - - FillPathProcessor processor = this.Verify(); - - ShapeOptions expectedOptions = this.shapeOptions; - Assert.Equal(expectedOptions, processor.Options.ShapeOptions); - Assert.Equal(1, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(PixelFormats.PixelAlphaCompositionMode.Src, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(PixelFormats.PixelColorBlendingMode.Normal, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.Equal(this.path, processor.Region); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearRectangle.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearRectangle.cs deleted file mode 100644 index c7d249851..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/ClearRectangle.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class ClearRectangle : BaseImageOperationsExtensionTest -{ - private readonly Brush brush = Brushes.Solid(Color.HotPink); - private RectangleF rectangle = new(10, 10, 20, 20); - - private RectangularPolygon RectanglePolygon => new(this.rectangle); - - [Fact] - public void Brush() - { - this.operations.Clear(new DrawingOptions(), this.brush, this.rectangle); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Region)); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void BrushDefaultOptions() - { - this.operations.Clear(this.brush, this.rectangle); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Region)); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void ColorSet() - { - this.operations.Clear(new DrawingOptions(), Color.Red, this.rectangle); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Region)); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.Clear(Color.Red, this.rectangle); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Region)); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawBezier.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawBezier.cs deleted file mode 100644 index 91d566714..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawBezier.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class DrawBezier : BaseImageOperationsExtensionTest -{ - private readonly SolidPen pen = Pens.Solid(Color.HotPink, 2); - private readonly PointF[] points = - [ - new(10, 10), - new(20, 20), - new(20, 50), - new(50, 10) - ]; - - private void VerifyPoints(PointF[] expectedPoints, IPath path) - { - Path innerPath = Assert.IsType(path); - ILineSegment segment = Assert.Single(innerPath.LineSegments); - CubicBezierLineSegment bezierSegment = Assert.IsType(segment); - Assert.Equal(expectedPoints, bezierSegment.ControlPoints.ToArray()); - - ISimplePath simplePath = Assert.Single(path.Flatten()); - Assert.False(simplePath.IsClosed); - } - - [Fact] - public void Pen() - { - this.operations.DrawBeziers(new DrawingOptions(), this.pen, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void PenDefaultOptions() - { - this.operations.DrawBeziers(this.pen, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void BrushAndThickness() - { - this.operations.DrawBeziers(new DrawingOptions(), this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeFill, processorPen.StrokeFill); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void BrushAndThicknessDefaultOptions() - { - this.operations.DrawBeziers(this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeFill, processorPen.StrokeFill); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThickness() - { - this.operations.DrawBeziers(new DrawingOptions(), Color.Red, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(Color.Red, brush.Color); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.DrawBeziers(Color.Red, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(Color.Red, brush.Color); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void JointAndEndCapStyle() - { - this.operations.DrawBeziers(new DrawingOptions(), this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } - - [Fact] - public void JointAndEndCapStyleDefaultOptions() - { - this.operations.DrawBeziers(this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawLine.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawLine.cs deleted file mode 100644 index 5ab5ae86c..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawLine.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class DrawLine : BaseImageOperationsExtensionTest -{ - private readonly SolidPen pen = Pens.Solid(Color.HotPink, 2); - private readonly PointF[] points = - [ - new(10, 10), - new(20, 20), - new(20, 50), - new(50, 10) - ]; - - private void VerifyPoints(PointF[] expectedPoints, IPath path) - { - ISimplePath simplePath = Assert.Single(path.Flatten()); - Assert.False(simplePath.IsClosed); - Assert.Equal(expectedPoints, simplePath.Points.ToArray()); - } - - [Fact] - public void Pen() - { - this.operations.DrawLine(new DrawingOptions(), this.pen, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void PenDefaultOptions() - { - this.operations.DrawLine(this.pen, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void BrushAndThickness() - { - this.operations.DrawLine(new DrawingOptions(), this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeFill, processorPen.StrokeFill); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void BrushAndThicknessDefaultOptions() - { - this.operations.DrawLine(this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeFill, processorPen.StrokeFill); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThickness() - { - this.operations.DrawLine(new DrawingOptions(), Color.Red, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(Color.Red, brush.Color); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.DrawLine(Color.Red, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, brush.Color); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void JointAndEndCapStyle() - { - this.operations.DrawLine(new DrawingOptions(), this.pen, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } - - [Fact] - public void JointAndEndCapStyleDefaultOptions() - { - this.operations.DrawLine(this.pen, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPath.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPath.cs deleted file mode 100644 index 8c283ed26..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPath.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class DrawPath : BaseImageOperationsExtensionTest -{ - private readonly SolidPen pen = Pens.Solid(Color.HotPink, 2); - private readonly IPath path = new EllipsePolygon(10, 10, 100); - - [Fact] - public void Pen() - { - this.operations.Draw(new DrawingOptions(), this.pen, this.path); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Path); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void PenDefaultOptions() - { - this.operations.Draw(this.pen, this.path); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Path); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void BrushAndThickness() - { - this.operations.Draw(new DrawingOptions(), this.pen.StrokeFill, 10, this.path); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Path); - Assert.Equal(this.pen.StrokeFill, processor.Pen.StrokeFill); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void BrushAndThicknessDefaultOptions() - { - this.operations.Draw(this.pen.StrokeFill, 10, this.path); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Path); - Assert.Equal(this.pen.StrokeFill, processor.Pen.StrokeFill); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThickness() - { - this.operations.Draw(new DrawingOptions(), Color.Red, 10, this.path); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Path); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, brush.Color); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.Draw(Color.Red, 10, this.path); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Path); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, brush.Color); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void JointAndEndCapStyle() - { - this.operations.Draw(new DrawingOptions(), this.pen.StrokeFill, 10, this.path); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } - - [Fact] - public void JointAndEndCapStyleDefaultOptions() - { - this.operations.Draw(this.pen.StrokeFill, 10, this.path); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPathCollection.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPathCollection.cs deleted file mode 100644 index cb104bbbd..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPathCollection.cs +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class DrawPathCollection : BaseImageOperationsExtensionTest -{ - private readonly SolidPen pen = Pens.Solid(Color.HotPink, 1); - private readonly IPath path1 = new Path(new LinearLineSegment( - [ - new Vector2(10, 10), - new Vector2(20, 10), - new Vector2(20, 10), - new Vector2(30, 10) - ])); - - private readonly IPath path2 = new Path(new LinearLineSegment( - [ - new Vector2(10, 10), - new Vector2(20, 10), - new Vector2(20, 10), - new Vector2(30, 10) - ])); - - private readonly IPathCollection pathCollection; - - public DrawPathCollection() - => this.pathCollection = new PathCollection(this.path1, this.path2); - - [Fact] - public void Pen() - { - this.operations.Draw(new DrawingOptions(), this.pen, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.NotEqual(this.shapeOptions, p.Options.ShapeOptions); - Assert.Equal(this.pen, p.Pen); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Path), - p => Assert.Equal(this.path2, p.Path)); - } - - [Fact] - public void PenDefaultOptions() - { - this.operations.Draw(this.pen, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.Equal(this.shapeOptions, p.Options.ShapeOptions); - Assert.Equal(this.pen, p.Pen); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Path), - p => Assert.Equal(this.path2, p.Path)); - } - - [Fact] - public void BrushAndThickness() - { - this.operations.Draw(new DrawingOptions(), this.pen.StrokeFill, 10, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.NotEqual(this.shapeOptions, p.Options.ShapeOptions); - Assert.Equal(this.pen.StrokeFill, p.Pen.StrokeFill); - SolidPen pPen = Assert.IsType(p.Pen); - Assert.Equal(10, pPen.StrokeWidth); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Path), - p => Assert.Equal(this.path2, p.Path)); - } - - [Fact] - public void BrushAndThicknessDefaultOptions() - { - this.operations.Draw(this.pen.StrokeFill, 10, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.Equal(this.shapeOptions, p.Options.ShapeOptions); - Assert.Equal(this.pen.StrokeFill, p.Pen.StrokeFill); - SolidPen pPen = Assert.IsType(p.Pen); - Assert.Equal(10, pPen.StrokeWidth); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Path), - p => Assert.Equal(this.path2, p.Path)); - } - - [Fact] - public void ColorAndThickness() - { - this.operations.Draw(new DrawingOptions(), Color.Pink, 10, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.NotEqual(this.shapeOptions, p.Options.ShapeOptions); - SolidBrush brush = Assert.IsType(p.Pen.StrokeFill); - Assert.Equal(Color.Pink, brush.Color); - SolidPen pPen = Assert.IsType(p.Pen); - Assert.Equal(10, pPen.StrokeWidth); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Path), - p => Assert.Equal(this.path2, p.Path)); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.Draw(Color.Pink, 10, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.Equal(this.shapeOptions, p.Options.ShapeOptions); - SolidBrush brush = Assert.IsType(p.Pen.StrokeFill); - Assert.Equal(Color.Pink, brush.Color); - SolidPen pPen = Assert.IsType(p.Pen); - Assert.Equal(10, pPen.StrokeWidth); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Path), - p => Assert.Equal(this.path2, p.Path)); - } - - [Fact] - public void JointAndEndCapStyle() - { - this.operations.Draw(new DrawingOptions(), this.pen.StrokeFill, 10, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.NotEqual(this.shapeOptions, p.Options.ShapeOptions); - SolidPen pPen = Assert.IsType(p.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, pPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, pPen.StrokeOptions.LineCap); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Path), - p => Assert.Equal(this.path2, p.Path)); - } - - [Fact] - public void JointAndEndCapStyleDefaultOptions() - { - this.operations.Draw(this.pen.StrokeFill, 10, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.Equal(this.shapeOptions, p.Options.ShapeOptions); - SolidPen pPen = Assert.IsType(p.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, pPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, pPen.StrokeOptions.LineCap); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Path), - p => Assert.Equal(this.path2, p.Path)); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPolygon.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPolygon.cs deleted file mode 100644 index fbc3cbee3..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawPolygon.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class DrawPolygon : BaseImageOperationsExtensionTest -{ - private readonly SolidPen pen = Pens.Solid(Color.HotPink, 2); - private readonly PointF[] points = - [ - new PointF(10, 10), - new PointF(10, 20), - new PointF(20, 20), - new PointF(25, 25), - new PointF(25, 10) - ]; - - private static void VerifyPoints(PointF[] expectedPoints, IPath path) - { - ISimplePath simplePath = Assert.Single(path.Flatten()); - Assert.True(simplePath.IsClosed); - Assert.Equal(expectedPoints, simplePath.Points.ToArray()); - } - - [Fact] - public void Pen() - { - this.operations.DrawPolygon(new DrawingOptions(), this.pen, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - VerifyPoints(this.points, processor.Path); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void PenDefaultOptions() - { - this.operations.DrawPolygon(this.pen, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - VerifyPoints(this.points, processor.Path); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void BrushAndThickness() - { - this.operations.DrawPolygon(new DrawingOptions(), this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeFill, processorPen.StrokeFill); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void BrushAndThicknessDefaultOptions() - { - this.operations.DrawPolygon(this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeFill, processorPen.StrokeFill); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThickness() - { - this.operations.DrawPolygon(new DrawingOptions(), Color.Red, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - VerifyPoints(this.points, processor.Path); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(Color.Red, brush.Color); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.DrawPolygon(Color.Red, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - VerifyPoints(this.points, processor.Path); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, brush.Color); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void JointAndEndCapStyle() - { - this.operations.DrawPolygon(new DrawingOptions(), this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } - - [Fact] - public void JointAndEndCapStyleDefaultOptions() - { - this.operations.DrawPolygon(this.pen.StrokeFill, 10, this.points); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - VerifyPoints(this.points, processor.Path); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawRectangle.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawRectangle.cs deleted file mode 100644 index 5e5ed3304..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/DrawRectangle.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class DrawRectangle : BaseImageOperationsExtensionTest -{ - private readonly SolidPen pen = Pens.Solid(Color.HotPink, 2); - private RectangleF rectangle = new(10, 10, 20, 20); - - private RectangularPolygon RectanglePolygon => new(this.rectangle); - - [Fact] - public void CorrectlySetsPenAndPath() - { - this.operations.Draw(new DrawingOptions(), this.pen, this.rectangle); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Path)); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void CorrectlySetsPenAndPathDefaultOptions() - { - this.operations.Draw(this.pen, this.rectangle); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Path)); - Assert.Equal(this.pen, processor.Pen); - } - - [Fact] - public void BrushAndThickness() - { - this.operations.Draw(new DrawingOptions(), this.pen.StrokeFill, 10, this.rectangle); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Path)); - Assert.NotEqual(this.pen, processor.Pen); - Assert.Equal(this.pen.StrokeFill, processor.Pen.StrokeFill); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void BrushAndThicknessDefaultOptions() - { - this.operations.Draw(this.pen.StrokeFill, 10, this.rectangle); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Path)); - Assert.NotEqual(this.pen, processor.Pen); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeFill, processorPen.StrokeFill); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThickness() - { - this.operations.Draw(new DrawingOptions(), Color.Red, 10, this.rectangle); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Path)); - Assert.NotEqual(this.pen, processor.Pen); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(Color.Red, brush.Color); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.Draw(Color.Red, 10, this.rectangle); - - DrawPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Path)); - Assert.NotEqual(this.pen, processor.Pen); - SolidBrush brush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, brush.Color); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(10, processorPen.StrokeWidth); - } - - [Fact] - public void JointAndEndCapStyle() - { - this.operations.Draw(new DrawingOptions(), this.pen.StrokeFill, 10, this.rectangle); - - DrawPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Path)); - Assert.NotEqual(this.pen, processor.Pen); - SolidPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(this.pen.StrokeOptions.LineJoin, processorPen.StrokeOptions.LineJoin); - Assert.Equal(this.pen.StrokeOptions.LineCap, processorPen.StrokeOptions.LineCap); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/Fill.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/Fill.cs deleted file mode 100644 index f4cdcd2bf..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/Fill.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class Fill : BaseImageOperationsExtensionTest -{ - private readonly DrawingOptions nonDefaultOptions = new(); - private readonly Brush brush = new SolidBrush(Color.HotPink); - - [Fact] - public void Brush() - { - this.operations.Fill(this.nonDefaultOptions, this.brush); - - FillProcessor processor = this.Verify(); - - DrawingOptions expectedOptions = this.nonDefaultOptions; - Assert.Equal(expectedOptions, processor.Options); - Assert.Equal(expectedOptions.GraphicsOptions.BlendPercentage, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(expectedOptions.GraphicsOptions.AlphaCompositionMode, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(expectedOptions.GraphicsOptions.ColorBlendingMode, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void BrushDefaultOptions() - { - this.operations.Fill(this.brush); - - FillProcessor processor = this.Verify(); - - GraphicsOptions expectedOptions = this.graphicsOptions; - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(expectedOptions.BlendPercentage, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(expectedOptions.AlphaCompositionMode, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(expectedOptions.ColorBlendingMode, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void ColorSet() - { - this.operations.Fill(this.nonDefaultOptions, Color.Red); - - FillProcessor processor = this.Verify(); - - DrawingOptions expectedOptions = this.nonDefaultOptions; - Assert.Equal(expectedOptions, processor.Options); - - Assert.Equal(expectedOptions.GraphicsOptions.BlendPercentage, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(expectedOptions.GraphicsOptions.AlphaCompositionMode, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(expectedOptions.GraphicsOptions.ColorBlendingMode, processor.Options.GraphicsOptions.ColorBlendingMode); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void ColorSetDefaultOptions() - { - this.operations.Fill(Color.Red); - - FillProcessor processor = this.Verify(); - - GraphicsOptions expectedOptions = this.graphicsOptions; - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(expectedOptions.BlendPercentage, processor.Options.GraphicsOptions.BlendPercentage); - Assert.Equal(expectedOptions.AlphaCompositionMode, processor.Options.GraphicsOptions.AlphaCompositionMode); - Assert.Equal(expectedOptions.ColorBlendingMode, processor.Options.GraphicsOptions.ColorBlendingMode); - - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPath.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPath.cs deleted file mode 100644 index bf895e21e..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPath.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class FillPath : BaseImageOperationsExtensionTest -{ - private readonly Brush brush = Brushes.Solid(Color.HotPink); - private readonly IPath path = new Star(1, 10, 5, 23, 56); - - [Fact] - public void Brush() - { - this.operations.Fill(new DrawingOptions(), this.brush, this.path); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Region); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void BrushDefaultOptions() - { - this.operations.Fill(this.brush, this.path); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Region); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void ColorSet() - { - this.operations.Fill(new DrawingOptions(), Color.Red, this.path); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Region); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.Fill(Color.Red, this.path); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.Equal(this.path, processor.Region); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPathBuilder.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPathBuilder.cs deleted file mode 100644 index 070f2577e..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPathBuilder.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class FillPathBuilder : BaseImageOperationsExtensionTest -{ - private readonly Brush brush = Brushes.Solid(Color.HotPink); - private readonly IPath path = null; - private readonly Action builder = pb => - { - pb.StartFigure(); - pb.AddLine(10, 10, 20, 20); - pb.AddLine(60, 450, 120, 340); - pb.AddLine(120, 340, 10, 10); - pb.CloseAllFigures(); - }; - - public FillPathBuilder() - { - PathBuilder pb = new(); - this.builder(pb); - this.path = pb.Build(); - } - - private void VerifyPoints(IPath expectedPath, IPath path) - { - ISimplePath simplePathExpected = Assert.Single(expectedPath.Flatten()); - PointF[] expectedPoints = simplePathExpected.Points.ToArray(); - - ISimplePath simplePath = Assert.Single(path.Flatten()); - Assert.True(simplePath.IsClosed); - Assert.Equal(expectedPoints, simplePath.Points.ToArray()); - } - - [Fact] - public void Brush() - { - this.operations.Fill(new DrawingOptions(), this.brush, this.builder); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.path, processor.Region); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void BrushDefaultOptions() - { - this.operations.Fill(this.brush, this.builder); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.path, processor.Region); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void ColorSet() - { - this.operations.Fill(new DrawingOptions(), Color.Red, this.builder); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.path, processor.Region); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.Fill(Color.Red, this.builder); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.path, processor.Region); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPathCollection.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPathCollection.cs deleted file mode 100644 index aca2d2e05..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPathCollection.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class FillPathCollection : BaseImageOperationsExtensionTest -{ - private readonly Color color = Color.HotPink; - private readonly SolidBrush brush = Brushes.Solid(Color.HotPink); - private readonly IPath path1 = new Path(new LinearLineSegment( - [ - new Vector2(10, 10), - new Vector2(20, 10), - new Vector2(20, 10), - new Vector2(30, 10) - ])); - - private readonly IPath path2 = new Path(new LinearLineSegment( - [ - new Vector2(10, 10), - new Vector2(20, 10), - new Vector2(20, 10), - new Vector2(30, 10) - ])); - - private readonly IPathCollection pathCollection; - - public FillPathCollection() - => this.pathCollection = new PathCollection(this.path1, this.path2); - - [Fact] - public void Brush() - { - this.operations.Fill(new DrawingOptions(), this.brush, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.NotEqual(this.shapeOptions, p.Options.ShapeOptions); - Assert.Equal(this.brush, p.Brush); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Region), - p => Assert.Equal(this.path2, p.Region)); - } - - [Fact] - public void BrushWithDefault() - { - this.operations.Fill(this.brush, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.Equal(this.shapeOptions, p.Options.ShapeOptions); - Assert.Equal(this.brush, p.Brush); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Region), - p => Assert.Equal(this.path2, p.Region)); - } - - [Fact] - public void ColorSet() - { - this.operations.Fill(new DrawingOptions(), Color.Pink, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.NotEqual(this.shapeOptions, p.Options.ShapeOptions); - SolidBrush brush = Assert.IsType(p.Brush); - Assert.Equal(Color.Pink, brush.Color); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Region), - p => Assert.Equal(this.path2, p.Region)); - } - - [Fact] - public void ColorWithDefault() - { - this.operations.Fill(Color.Pink, this.pathCollection); - IEnumerable processors = this.VerifyAll(); - - Assert.All(processors, p => - { - Assert.Equal(this.shapeOptions, p.Options.ShapeOptions); - SolidBrush brush = Assert.IsType(p.Brush); - Assert.Equal(Color.Pink, brush.Color); - }); - - Assert.Collection( - processors, - p => Assert.Equal(this.path1, p.Region), - p => Assert.Equal(this.path2, p.Region)); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPolygon.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPolygon.cs deleted file mode 100644 index bac4ffb04..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillPolygon.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class FillPolygon : BaseImageOperationsExtensionTest -{ - private readonly Brush brush = Brushes.Solid(Color.HotPink); - private readonly PointF[] path = - [ - new PointF(10, 10), - new PointF(10, 20), - new PointF(20, 20), - new PointF(25, 25), - new PointF(25, 10) - ]; - - private void VerifyPoints(PointF[] expectedPoints, IPath path) - { - ISimplePath simplePath = Assert.Single(path.Flatten()); - Assert.True(simplePath.IsClosed); - Assert.Equal(expectedPoints, simplePath.Points.ToArray()); - } - - [Fact] - public void Brush() - { - this.operations.FillPolygon(new DrawingOptions(), this.brush, this.path); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.path, processor.Region); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void BrushDefaultOptions() - { - this.operations.FillPolygon(this.brush, this.path); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.path, processor.Region); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void ColorSet() - { - this.operations.FillPolygon(new DrawingOptions(), Color.Red, this.path); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.path, processor.Region); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.FillPolygon(Color.Red, this.path); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - this.VerifyPoints(this.path, processor.Region); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillRectangle.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillRectangle.cs deleted file mode 100644 index a13537d46..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Paths/FillRectangle.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Tests.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Paths; - -public class FillRectangle : BaseImageOperationsExtensionTest -{ - private readonly Brush brush = Brushes.Solid(Color.HotPink); - private RectangleF rectangle = new(10, 10, 20, 20); - - private RectangularPolygon RectanglePolygon => new(this.rectangle); - - [Fact] - public void Brush() - { - this.operations.Fill(new DrawingOptions(), this.brush, this.rectangle); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Region)); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void BrushDefaultOptions() - { - this.operations.Fill(this.brush, this.rectangle); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Region)); - Assert.Equal(this.brush, processor.Brush); - } - - [Fact] - public void ColorSet() - { - this.operations.Fill(new DrawingOptions(), Color.Red, this.rectangle); - - FillPathProcessor processor = this.Verify(); - - Assert.NotEqual(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Region)); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } - - [Fact] - public void ColorAndThicknessDefaultOptions() - { - this.operations.Fill(Color.Red, this.rectangle); - - FillPathProcessor processor = this.Verify(); - - Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); - Assert.True(RectangularPolygonValueComparer.Equals(this.RectanglePolygon, processor.Region)); - Assert.NotEqual(this.brush, processor.Brush); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/ProcessWithCanvas.cs b/tests/ImageSharp.Drawing.Tests/Drawing/ProcessWithCanvas.cs new file mode 100644 index 000000000..08a859061 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Drawing/ProcessWithCanvas.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; + +public class ProcessWithCanvas : BaseImageOperationsExtensionTest +{ + private readonly DrawingOptions nonDefaultOptions = new(); + + [Fact] + public void CanvasActionDefaultOptions() + { + this.operations.ProcessWithCanvas(canvas => canvas.Clear(Brushes.Solid(Color.Red))); + + ProcessWithCanvasProcessor processor = this.Verify(); + + GraphicsOptions expectedOptions = this.graphicsOptions; + Assert.Equal(this.shapeOptions, processor.Options.ShapeOptions); + Assert.Equal(expectedOptions.BlendPercentage, processor.Options.GraphicsOptions.BlendPercentage); + Assert.Equal(expectedOptions.AlphaCompositionMode, processor.Options.GraphicsOptions.AlphaCompositionMode); + Assert.Equal(expectedOptions.ColorBlendingMode, processor.Options.GraphicsOptions.ColorBlendingMode); + } + + [Fact] + public void CanvasActionWithOptions() + { + this.operations.ProcessWithCanvas( + this.nonDefaultOptions, + canvas => canvas.Clear(Brushes.Solid(Color.Red))); + + ProcessWithCanvasProcessor processor = this.Verify(); + Assert.Equal(this.nonDefaultOptions, processor.Options); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/RecolorImageTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/RecolorImageTests.cs deleted file mode 100644 index 289971c86..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/RecolorImageTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class RecolorImageTests -{ - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, "Yellow", "Pink", 0.2f)] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] - [WithTestPatternImage(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] - [WithTestPatternImage(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.6f)] - public void Recolor(TestImageProvider provider, string sourceColorName, string targetColorName, float threshold) - where TPixel : unmanaged, IPixel - { - Color sourceColor = TestUtils.GetColorByName(sourceColorName); - Color targetColor = TestUtils.GetColorByName(targetColorName); - RecolorBrush brush = new(sourceColor, targetColor, threshold); - - FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; - provider.RunValidatingProcessorTest(x => x.Fill(brush), testInfo); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] - [WithTestPatternImage(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] - public void Recolor_InBox(TestImageProvider provider, string sourceColorName, string targetColorName, float threshold) - where TPixel : unmanaged, IPixel - { - Color sourceColor = TestUtils.GetColorByName(sourceColorName); - Color targetColor = TestUtils.GetColorByName(targetColorName); - RecolorBrush brush = new(sourceColor, targetColor, threshold); - - FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; - provider.RunValidatingProcessorTest( - x => - { - Size size = x.GetCurrentSize(); - Rectangle rectangle = new(0, (size.Height / 2) - (size.Height / 4), size.Width, size.Height / 2); - x.Fill(brush, rectangle); - }, - testInfo); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/SolidBezierTests.cs deleted file mode 100644 index 11e80c5c2..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/SolidBezierTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class SolidBezierTests -{ - [Theory] - [WithBlankImage(500, 500, PixelTypes.Rgba32)] - public void FilledBezier(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - ]; - - Color blue = Color.Blue; - Color hotPink = Color.HotPink; - - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BackgroundColor(blue)); - image.Mutate(x => x.Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } - } - - [Theory] - [WithBlankImage(500, 500, PixelTypes.Rgba32)] - public void OverlayByFilledPolygonOpacity(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - PointF[] simplePath = - [ - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - ]; - - Color color = Color.HotPink.WithAlpha(150 / 255F); - - using (Image image = provider.GetImage() as Image) - { - image.Mutate(x => x.BackgroundColor(Color.Blue)); - image.Mutate(x => x.Fill(color, new Polygon(new CubicBezierLineSegment(simplePath)))); - image.DebugSave(provider); - image.CompareToReferenceOutput(provider); - } - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/SolidFillBlendedShapesTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/SolidFillBlendedShapesTests.cs deleted file mode 100644 index f6464394e..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/SolidFillBlendedShapesTests.cs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing; - -[GroupOutput("Drawing")] -public class SolidFillBlendedShapesTests -{ - public static IEnumerable Modes { get; } = GetAllModeCombinations(); - - private static IEnumerable GetAllModeCombinations() - { - foreach (object composition in Enum.GetValues(typeof(PixelAlphaCompositionMode))) - { - foreach (object blending in Enum.GetValues(typeof(PixelColorBlendingMode))) - { - yield return [blending, composition]; - } - } - } - - [Theory] - [WithBlankImage(nameof(Modes), 250, 250, PixelTypes.Rgba32)] -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable SA1300 // Element should begin with upper-case letter - public void _1DarkBlueRect_2BlendHotPinkRect( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition) - where TPixel : unmanaged, IPixel -#pragma warning restore SA1300 // Element should begin with upper-case letter -#pragma warning restore IDE1006 // Naming Styles - { - using (Image img = provider.GetImage()) - { - int scaleX = img.Width / 100; - int scaleY = img.Height / 100; - img.Mutate( - x => x.Fill( - Color.DarkBlue, - new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)) - - .Fill( - new DrawingOptions - { - GraphicsOptions = - { - Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition - } - }, - Color.HotPink, - new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); - - VerifyImage(provider, blending, composition, img); - } - } - - [Theory] - [WithBlankImage(nameof(Modes), 250, 250, PixelTypes.Rgba32)] -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable SA1300 // Element should begin with upper-case letter - public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition) - where TPixel : unmanaged, IPixel -#pragma warning restore SA1300 // Element should begin with upper-case letter -#pragma warning restore IDE1006 // Naming Styles - { - using (Image img = provider.GetImage()) - { - int scaleX = img.Width / 100; - int scaleY = img.Height / 100; - img.Mutate( - x => x.Fill( - Color.DarkBlue, - new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); - img.Mutate( - x => x.Fill( - new DrawingOptions - { - GraphicsOptions = new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition } - }, - Color.HotPink, - new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); - img.Mutate( - x => x.Fill( - new DrawingOptions - { - GraphicsOptions = new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition } - }, - Color.Transparent, - new EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); - - VerifyImage(provider, blending, composition, img); - } - } - - [Theory] - [WithBlankImage(nameof(Modes), 250, 250, PixelTypes.Rgba32)] -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable SA1300 // Element should begin with upper-case letter - public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition) - where TPixel : unmanaged, IPixel -#pragma warning restore SA1300 // Element should begin with upper-case letter -#pragma warning restore IDE1006 // Naming Styles - { - using (Image img = provider.GetImage()) - { - int scaleX = img.Width / 100; - int scaleY = img.Height / 100; - img.Mutate( - x => x.Fill( - Color.DarkBlue, - new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); - img.Mutate( - x => x.Fill( - new DrawingOptions - { - GraphicsOptions = new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition } - }, - Color.HotPink, - new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); - - Color transparentRed = Color.Red.WithAlpha(0.5f); - - img.Mutate( - x => x.Fill( - new DrawingOptions - { - GraphicsOptions = new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition } - }, - transparentRed, - new EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); - - VerifyImage(provider, blending, composition, img); - } - } - - [Theory] - [WithBlankImage(nameof(Modes), 250, 250, PixelTypes.Rgba32)] -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable SA1300 // Element should begin with upper-case letter - public void _1DarkBlueRect_2BlendBlackEllipse( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition) - where TPixel : unmanaged, IPixel -#pragma warning restore SA1300 // Element should begin with upper-case letter -#pragma warning restore IDE1006 // Naming Styles - { - using (Image dstImg = provider.GetImage(), srcImg = provider.GetImage()) - { - int scaleX = dstImg.Width / 100; - int scaleY = dstImg.Height / 100; - - dstImg.Mutate( - x => x.Fill( - Color.DarkBlue, - new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); - - srcImg.Mutate( - x => x.Fill( - Color.Black, - new EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); - - dstImg.Mutate( - x => x.DrawImage(srcImg, new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition })); - - VerifyImage(provider, blending, composition, dstImg); - } - } - - private static void VerifyImage( - TestImageProvider provider, - PixelColorBlendingMode blending, - PixelAlphaCompositionMode composition, - Image img) - where TPixel : unmanaged, IPixel - { - img.DebugSave( - provider, - new { composition, blending }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - ImageComparer comparer = ImageComparer.TolerantPercentage(0.01f, 3); - img.CompareFirstFrameToReferenceOutput( - comparer, - provider, - new { composition, blending }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Text/DrawText.cs deleted file mode 100644 index 4355855ee..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Text/DrawText.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.Fonts; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Text; -using SixLabors.ImageSharp.Drawing.Tests.Processing; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Text; - -public class DrawText : BaseImageOperationsExtensionTest -{ - private readonly FontCollection fontCollection; - private readonly RichTextOptions textOptions; - private readonly DrawingOptions otherDrawingOptions = new() - { - GraphicsOptions = new GraphicsOptions() - }; - - private readonly Font font; - - public DrawText() - { - this.fontCollection = new FontCollection(); - this.font = this.fontCollection.Add(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")).CreateFont(12); - this.textOptions = new RichTextOptions(this.font) { WrappingLength = 99 }; - } - - [Fact] - public void FillsForEachACharacterWhenBrushSetAndNotPen() - { - this.operations.DrawText( - this.otherDrawingOptions, - "123", - this.font, - Brushes.Solid(Color.Red), - null, - Vector2.Zero); - - DrawTextProcessor processor = this.Verify(0); - Assert.NotEqual(this.textOptions, processor.TextOptions); - Assert.NotEqual(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void FillsForEachACharacterWhenBrushSetAndNotPenDefaultOptions() - { - this.operations.DrawText(this.textOptions, "123", Brushes.Solid(Color.Red)); - - DrawTextProcessor processor = this.Verify(0); - Assert.Equal(this.textOptions, processor.TextOptions); - Assert.Equal(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void FillsForEachACharacterWhenBrushSet() - { - this.operations.DrawText(this.otherDrawingOptions, "123", this.font, Brushes.Solid(Color.Red), Vector2.Zero); - - DrawTextProcessor processor = this.Verify(0); - Assert.NotEqual(this.textOptions, processor.TextOptions); - Assert.NotEqual(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void FillsForEachACharacterWhenBrushSetDefaultOptions() - { - this.operations.DrawText(this.textOptions, "123", Brushes.Solid(Color.Red)); - - DrawTextProcessor processor = this.Verify(0); - Assert.Equal(this.textOptions, processor.TextOptions); - Assert.Equal(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void FillsForEachACharacterWhenColorSet() - { - this.operations.DrawText(this.otherDrawingOptions, "123", this.font, Color.Red, Vector2.Zero); - - DrawTextProcessor processor = this.Verify(0); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - Assert.NotEqual(this.textOptions, processor.TextOptions); - Assert.NotEqual(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void FillsForEachACharacterWhenColorSetDefaultOptions() - { - this.operations.DrawText(this.textOptions, "123", Color.Red); - - DrawTextProcessor processor = this.Verify(0); - - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - Assert.Equal(this.textOptions, processor.TextOptions); - Assert.Equal(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void DrawForEachACharacterWhenPenSetAndNotBrush() - { - this.operations.DrawText( - this.otherDrawingOptions, - "123", - this.font, - null, - Pens.Dash(Color.Red, 1), - Vector2.Zero); - - DrawTextProcessor processor = this.Verify(0); - Assert.NotEqual(this.textOptions, processor.TextOptions); - Assert.NotEqual(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void DrawForEachACharacterWhenPenSetAndNotBrushDefaultOptions() - { - this.operations.DrawText(this.textOptions, "123", Pens.Dash(Color.Red, 1)); - - DrawTextProcessor processor = this.Verify(0); - Assert.Equal(this.textOptions, processor.TextOptions); - Assert.Equal(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void DrawForEachACharacterWhenPenSet() - { - this.operations.DrawText(this.otherDrawingOptions, "123", this.font, Pens.Dash(Color.Red, 1), Vector2.Zero); - - DrawTextProcessor processor = this.Verify(0); - Assert.NotEqual(this.textOptions, processor.TextOptions); - Assert.NotEqual(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void DrawForEachACharacterWhenPenSetDefaultOptions() - { - this.operations.DrawText(this.textOptions, "123", Pens.Dash(Color.Red, 1)); - - DrawTextProcessor processor = this.Verify(0); - - Assert.Equal("123", processor.Text); - Assert.Equal(this.font, processor.TextOptions.Font); - SolidBrush penBrush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, penBrush.Color); - PatternPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(1, processorPen.StrokeWidth); - Assert.Equal(PointF.Empty, processor.Location); - Assert.Equal(this.textOptions, processor.TextOptions); - Assert.Equal(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } - - [Fact] - public void DrawForEachACharacterWhenPenSetAndFillFroEachWhenBrushSet() - { - this.operations.DrawText( - this.otherDrawingOptions, - "123", - this.font, - Brushes.Solid(Color.Red), - Pens.Dash(Color.Red, 1), - Vector2.Zero); - - DrawTextProcessor processor = this.Verify(0); - - Assert.Equal("123", processor.Text); - Assert.Equal(this.font, processor.TextOptions.Font); - SolidBrush brush = Assert.IsType(processor.Brush); - Assert.Equal(Color.Red, brush.Color); - Assert.Equal(PointF.Empty, processor.Location); - SolidBrush penBrush = Assert.IsType(processor.Pen.StrokeFill); - Assert.Equal(Color.Red, penBrush.Color); - PatternPen processorPen = Assert.IsType(processor.Pen); - Assert.Equal(1, processorPen.StrokeWidth); - Assert.NotEqual(this.textOptions, processor.TextOptions); - Assert.NotEqual(this.graphicsOptions, processor.DrawingOptions.GraphicsOptions); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Drawing/Text/DrawTextOnImageTests.cs b/tests/ImageSharp.Drawing.Tests/Drawing/Text/DrawTextOnImageTests.cs deleted file mode 100644 index e7379712a..000000000 --- a/tests/ImageSharp.Drawing.Tests/Drawing/Text/DrawTextOnImageTests.cs +++ /dev/null @@ -1,979 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Text; -using SixLabors.Fonts; -using SixLabors.Fonts.Unicode; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Drawing.Text; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Text; - -[GroupOutput("Drawing/Text")] -[ValidateDisposedMemoryAllocations] -public class DrawTextOnImageTests -{ - private const string AB = "AB\nAB"; - - private const string TestText = "Sphinx of black quartz, judge my vow\n0123456789"; - - private static readonly ImageComparer TextDrawingComparer = ImageComparer.TolerantPercentage(1e-2f); - - private static readonly ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(0.0069F); - - public DrawTextOnImageTests(ITestOutputHelper output) - => this.Output = output; - - private ITestOutputHelper Output { get; } - - [Theory] - [WithSolidFilledImages(1276, 336, "White", PixelTypes.Rgba32, ColorFontSupport.ColrV0)] - [WithSolidFilledImages(1276, 336, "White", PixelTypes.Rgba32, ColorFontSupport.None)] - public void EmojiFontRendering(TestImageProvider provider, ColorFontSupport colorFontSupport) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 70); - FontFamily emojiFontFamily = CreateFont(TestFonts.TwemojiMozilla, 36).Family; - - Color color = Color.Black; - string text = "A short piece of text 😀 with an emoji"; - - provider.VerifyOperation( - TextDrawingComparer, - img => - { - RichTextOptions textOptions = new(font) - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Center, - FallbackFontFamilies = [emojiFontFamily], - ColorFontSupport = colorFontSupport, - Origin = new PointF(img.Width / 2, img.Height / 2) - }; - - img.Mutate(i => i.DrawText(textOptions, text, color)); - }, - $"ColorFontsEnabled-{colorFontSupport == ColorFontSupport.ColrV0}"); - } - - [Theory] - [WithSolidFilledImages(400, 200, "White", PixelTypes.Rgba32)] - public void FallbackFontRendering(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // https://github.com/SixLabors/Fonts/issues/171 - FontCollection collection = new(); - Font whitney = CreateFont(TestFonts.WhitneyBook, 25); - FontFamily malgun = CreateFont(TestFonts.Malgun, 25).Family; - - Color color = Color.Black; - const string text = "亞DARKSOUL亞"; - - provider.VerifyOperation( - TextDrawingComparer, - img => - { - RichTextOptions textOptions = new(whitney) - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Center, - FallbackFontFamilies = [malgun], - KerningMode = KerningMode.Standard, - Origin = new PointF(img.Width / 2, img.Height / 2) - }; - - img.Mutate(i => i.DrawText(textOptions, text, color)); - }); - } - - [Theory] - [WithSolidFilledImages(276, 336, "White", PixelTypes.Rgba32)] - public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 36); - Color color = Color.Black; - const string text = "A short piece of text"; - - using Image img = provider.GetImage(); - - // Measure the text size - FontRectangle size = TextMeasurer.MeasureSize(text, new RichTextOptions(font)); - - // Find out how much we need to scale the text to fill the space (up or down) - float scalingFactor = Math.Min(img.Width / size.Width, img.Height / size.Height); - - // Create a new font - Font scaledFont = new(font, scalingFactor * font.Size); - RichTextOptions textOptions = new(scaledFont) - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Center, - Origin = new PointF(img.Width / 2, img.Height / 2) - }; - - img.Mutate(i => i.DrawText(textOptions, text, color)); - } - - [Theory] - [WithSolidFilledImages(1500, 500, "White", PixelTypes.Rgba32)] - public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688_2(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 39); - string text = new('a', 10000); - Color color = Color.Black; - PointF point = new(100, 100); - - using Image img = provider.GetImage(); - img.Mutate(ctx => ctx.DrawText(text, font, color, point)); - } - - [Theory] - [WithSolidFilledImages(200, 200, "White", PixelTypes.Rgba32)] - public void OpenSansJWithNoneZeroShouldntExtendPastGlyphe(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 50); - Color color = Color.Black; - - using Image img = provider.GetImage(); - img.Mutate(ctx => ctx.DrawText(TestText, font, Color.Black, new PointF(-50, 2))); - - Assert.Equal(Color.White.ToPixel(), img[173, 2]); - } - - [Theory] - [WithSolidFilledImages(20, 50, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.OpenSans, "i")] - [WithSolidFilledImages(200, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.SixLaborsSampleAB, AB)] - [WithSolidFilledImages(900, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.OpenSans, TestText)] - [WithSolidFilledImages(400, 45, "White", PixelTypes.Rgba32, 20, 0, 0, TestFonts.OpenSans, TestText)] - [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 50, TestFonts.OpenSans, TestText)] - public void FontShapesAreRenderedCorrectly( - TestImageProvider provider, - int fontSize, - int x, - int y, - string fontName, - string text) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(fontName, fontSize); - - provider.RunValidatingProcessorTest( - c => c.DrawText(text, font, Color.Black, new PointF(x, y)), - $"{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(50, 50, "White", PixelTypes.Rgba32, 50, 25, 25, TestFonts.OpenSans, "i", 45, 25, 25)] - [WithSolidFilledImages(200, 200, "White", PixelTypes.Rgba32, 50, 100, 100, TestFonts.SixLaborsSampleAB, AB, 45, 100, 100)] - [WithSolidFilledImages(1100, 1100, "White", PixelTypes.Rgba32, 50, 550, 550, TestFonts.OpenSans, TestText, 45, 550, 550)] - [WithSolidFilledImages(400, 400, "White", PixelTypes.Rgba32, 20, 200, 200, TestFonts.OpenSans, TestText, 45, 200, 200)] - public void FontShapesAreRenderedCorrectly_WithRotationApplied( - TestImageProvider provider, - int fontSize, - int x, - int y, - string fontName, - string text, - float angle, - float rotationOriginX, - float rotationOriginY) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(fontName, fontSize); - float radians = GeometryUtilities.DegreeToRadian(angle); - - RichTextOptions textOptions = new(font) - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Center, - Origin = new PointF(x, y) - }; - - provider.RunValidatingProcessorTest( - x => x - .SetDrawingTransform(Matrix3x2.CreateRotation(radians, new Vector2(rotationOriginX, rotationOriginY))) - .DrawText(textOptions, text, Color.Black), - $"F({fontName})-S({fontSize})-A({angle})-{ToTestOutputDisplayText(text)}-({x},{y})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(50, 50, "White", PixelTypes.Rgba32, 50, 25, 25, TestFonts.OpenSans, "i", -12, 0, 25, 25)] - [WithSolidFilledImages(200, 200, "White", PixelTypes.Rgba32, 50, 100, 100, TestFonts.SixLaborsSampleAB, AB, 10, 0, 100, 100)] - [WithSolidFilledImages(1100, 1100, "White", PixelTypes.Rgba32, 50, 550, 550, TestFonts.OpenSans, TestText, 0, 10, 550, 550)] - [WithSolidFilledImages(400, 400, "White", PixelTypes.Rgba32, 20, 200, 200, TestFonts.OpenSans, TestText, 0, -10, 200, 200)] - public void FontShapesAreRenderedCorrectly_WithSkewApplied( - TestImageProvider provider, - int fontSize, - int x, - int y, - string fontName, - string text, - float angleX, - float angleY, - float rotationOriginX, - float rotationOriginY) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(fontName, fontSize); - float radianX = GeometryUtilities.DegreeToRadian(angleX); - float radianY = GeometryUtilities.DegreeToRadian(angleY); - - RichTextOptions textOptions = new(font) - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Center, - Origin = new PointF(x, y) - }; - - provider.RunValidatingProcessorTest( - x => x - .SetDrawingTransform(Matrix3x2.CreateSkew(radianX, radianY, new Vector2(rotationOriginX, rotationOriginY))) - .DrawText(textOptions, text, Color.Black), - $"F({fontName})-S({fontSize})-A({angleX},{angleY})-{ToTestOutputDisplayText(text)}-({x},{y})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - /// - /// Based on: - /// https://github.com/SixLabors/ImageSharp/issues/572 - /// - [Theory] - [WithSolidFilledImages(2480, 3508, "White", PixelTypes.Rgba32)] - public void FontShapesAreRenderedCorrectly_LargeText( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 36); - - StringBuilder sb = new(); - string str = Repeat(" ", 78) + "THISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDS"; - sb.Append(str); - - string newLines = Repeat("\r\n", 61); - sb.Append(newLines); - - for (int i = 0; i < 10; i++) - { - sb.AppendLine(str); - } - - // Strict comparer, because the image is sparse: - ImageComparer comparer = ImageComparer.TolerantPercentage(0.0001F); - - provider.VerifyOperation( - comparer, - img => img.Mutate(c => c.DrawText(sb.ToString(), font, Color.Black, new PointF(10, 1))), - false, - false); - } - - [Theory] - [WithSolidFilledImages(400, 550, "White", PixelTypes.Rgba32, 1, 5, true)] - [WithSolidFilledImages(400, 550, "White", PixelTypes.Rgba32, 1.5, 3, true)] - [WithSolidFilledImages(400, 550, "White", PixelTypes.Rgba32, 2, 2, true)] - [WithSolidFilledImages(400, 100, "White", PixelTypes.Rgba32, 1, 5, false)] - [WithSolidFilledImages(400, 100, "White", PixelTypes.Rgba32, 1.5, 3, false)] - [WithSolidFilledImages(400, 100, "White", PixelTypes.Rgba32, 2, 2, false)] - public void FontShapesAreRenderedCorrectly_WithLineSpacing( - TestImageProvider provider, - float lineSpacing, - int lineCount, - bool wrap) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 16); - - StringBuilder sb = new(); - string str = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna."; - - for (int i = 0; i < lineCount; i++) - { - sb.AppendLine(str); - } - - RichTextOptions textOptions = new(font) - { - KerningMode = KerningMode.Standard, - VerticalAlignment = VerticalAlignment.Top, - HorizontalAlignment = HorizontalAlignment.Left, - LineSpacing = lineSpacing, - Origin = new PointF(10, 1) - }; - - if (wrap) - { - textOptions.WrappingLength = 300; - } - - Color color = Color.Black; - - // NET472 is 0.0045 different. - ImageComparer comparer = ImageComparer.TolerantPercentage(0.0046F); - - provider.VerifyOperation( - comparer, - img => img.Mutate(c => c.DrawText(textOptions, sb.ToString(), color)), - $"linespacing_{lineSpacing}_linecount_{lineCount}_wrap_{wrap}", - false, - false); - } - - [Theory] - [WithSolidFilledImages(200, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.SixLaborsSampleAB, AB)] - [WithSolidFilledImages(900, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.OpenSans, TestText)] - [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 50, TestFonts.OpenSans, TestText)] - public void FontShapesAreRenderedCorrectlyWithAPen( - TestImageProvider provider, - int fontSize, - int x, - int y, - string fontName, - string text) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(fontName, fontSize); - Color color = Color.Black; - - provider.VerifyOperation( - OutlinedTextDrawingComparer, - img => img.Mutate(c => c.DrawText(text, new Font(font, fontSize), Pens.Solid(color, 1), new PointF(x, y))), - $"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(200, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.SixLaborsSampleAB, AB)] - [WithSolidFilledImages(900, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.OpenSans, TestText)] - [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 50, TestFonts.OpenSans, TestText)] - public void FontShapesAreRenderedCorrectlyWithAPenPatterned( - TestImageProvider provider, - int fontSize, - int x, - int y, - string fontName, - string text) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(fontName, fontSize); - Color color = Color.Black; - - provider.VerifyOperation( - OutlinedTextDrawingComparer, - img => img.Mutate(c => c.DrawText(text, new Font(font, fontSize), Pens.DashDot(color, 3), new PointF(x, y))), - $"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, TestFonts.OpenSans)] - public void TextPositioningIsRobust(TestImageProvider provider, string fontName) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(fontName, 30); - - string text = Repeat( - "Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!\n", - 20); - - RichTextOptions textOptions = new(font) - { - WrappingLength = 1000, - Origin = new PointF(10, 50) - }; - - string details = fontName.Replace(" ", string.Empty); - - // Based on the reported 0.1755% difference with AccuracyMultiple = 8 - // We should avoid quality regressions leading to higher difference! - ImageComparer comparer = ImageComparer.TolerantPercentage(0.2f); - - provider.RunValidatingProcessorTest( - x => x.DrawText(textOptions, text, Color.Black), - details, - comparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } - - [Fact] - public void CanDrawTextWithEmptyPath() - { - // The following font/text combination generates an empty path. - Font font = CreateFont(TestFonts.WendyOne, 72); - const string text = "Hello\0World"; - RichTextOptions textOptions = new(font); - FontRectangle textSize = TextMeasurer.MeasureSize(text, textOptions); - - Assert.NotEqual(FontRectangle.Empty, textSize); - - using Image image = new(Configuration.Default, (int)textSize.Width + 20, (int)textSize.Height + 20); - image.Mutate(x => x.DrawText( - text, - font, - Color.Black, - Vector2.Zero)); - } - - [Theory] - [WithSolidFilledImages(300, 200, nameof(Color.White), PixelTypes.Rgba32, TestFonts.OpenSans, 32, 75F)] - [WithSolidFilledImages(300, 200, nameof(Color.White), PixelTypes.Rgba32, TestFonts.OpenSans, 40, 90F)] - public void CanRotateFilledFont_Issue175( - TestImageProvider provider, - string fontName, - int fontSize, - float angle) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(fontName, fontSize); - const string text = "QuickTYZ"; - AffineTransformBuilder builder = new AffineTransformBuilder().AppendRotationDegrees(angle); - - RichTextOptions textOptions = new(font); - FontRectangle advance = TextMeasurer.MeasureAdvance(text, textOptions); - Matrix3x2 transform = builder.BuildMatrix(Rectangle.Round(new RectangleF(advance.X, advance.Y, advance.Width, advance.Height))); - - provider.RunValidatingProcessorTest( - x => x.SetDrawingTransform(transform).DrawText(textOptions, text, Color.Black), - $"F({fontName})-S({fontSize})-A({angle})-{ToTestOutputDisplayText(text)})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(300, 200, nameof(Color.White), PixelTypes.Rgba32, TestFonts.OpenSans, 32, 75F, 1)] - [WithSolidFilledImages(300, 200, nameof(Color.White), PixelTypes.Rgba32, TestFonts.OpenSans, 40, 90F, 2)] - public void CanRotateOutlineFont_Issue175( - TestImageProvider provider, - string fontName, - int fontSize, - float angle, - int strokeWidth) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(fontName, fontSize); - const string text = "QuickTYZ"; - AffineTransformBuilder builder = new AffineTransformBuilder().AppendRotationDegrees(angle); - - RichTextOptions textOptions = new(font); - FontRectangle advance = TextMeasurer.MeasureAdvance(text, textOptions); - Matrix3x2 transform = builder.BuildMatrix(Rectangle.Round(new RectangleF(advance.X, advance.Y, advance.Width, advance.Height))); - - provider.RunValidatingProcessorTest( - x => x.SetDrawingTransform(transform) - .DrawText(textOptions, text, Pens.Solid(Color.Black, strokeWidth)), - $"F({fontName})-S({fontSize})-A({angle})-STR({strokeWidth})-{ToTestOutputDisplayText(text)})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(500, 200, nameof(Color.Black), PixelTypes.Rgba32, 32)] - [WithSolidFilledImages(500, 300, nameof(Color.Black), PixelTypes.Rgba32, 40)] - public void DrawRichText( - TestImageProvider provider, - int fontSize) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, fontSize); - Font font2 = CreateFont(TestFonts.OpenSans, fontSize * 1.5f); - const string text = "The quick brown fox jumps over the lazy dog"; - - RichTextOptions textOptions = new(font) - { - Origin = new Vector2(15), - WrappingLength = 400, - TextRuns = - [ - new RichTextRun - { - Start = 0, - End = 3, - OverlinePen = Pens.Solid(Color.Yellow, 1), - StrikeoutPen = Pens.Solid(Color.HotPink, 5), - }, - - new RichTextRun - { - Start = 4, - End = 10, - TextDecorations = TextDecorations.Strikeout, - StrikeoutPen = Pens.Solid(Color.Red), - OverlinePen = Pens.Solid(Color.Green, 9), - Brush = Brushes.Solid(Color.Red), - }, - - new RichTextRun - { - Start = 10, - End = 13, - Font = font2, - TextDecorations = TextDecorations.Strikeout, - StrikeoutPen = Pens.Solid(Color.White, 6), - OverlinePen = Pens.Solid(Color.Orange, 2), - }, - - new RichTextRun - { - Start = 19, - End = 23, - TextDecorations = TextDecorations.Underline, - UnderlinePen = Pens.Dot(Color.Fuchsia, 5), - Brush = Brushes.Solid(Color.Blue), - }, - - new RichTextRun - { - Start = 23, - End = 25, - TextDecorations = TextDecorations.Underline, - UnderlinePen = Pens.Solid(Color.White), - } - ] - }; - provider.RunValidatingProcessorTest( - x => x.DrawText(textOptions, text, Color.White), - $"RichText-F({fontSize})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(500, 200, nameof(Color.Black), PixelTypes.Rgba32, 32)] - [WithSolidFilledImages(500, 300, nameof(Color.Black), PixelTypes.Rgba32, 40)] - public void DrawRichTextArabic( - TestImageProvider provider, - int fontSize) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.MeQuranVolyNewmet, fontSize); - string text = "بِسْمِ ٱللَّهِ ٱلرَّحْمَٟنِ ٱلرَّحِيمِ"; - - RichTextOptions textOptions = new(font) - { - Origin = new Vector2(15), - WrappingLength = 400, - TextRuns = - [ - new RichTextRun { Start = 0, End = CodePoint.GetCodePointCount(text.AsSpan()), TextDecorations = TextDecorations.Underline } - ] - }; - provider.RunValidatingProcessorTest( - x => x.DrawText(textOptions, text, Color.White), - $"RichText-Arabic-F({fontSize})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(500, 200, nameof(Color.Black), PixelTypes.Rgba32, 32)] - [WithSolidFilledImages(500, 300, nameof(Color.Black), PixelTypes.Rgba32, 40)] - public void DrawRichTextRainbow( - TestImageProvider provider, - int fontSize) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, fontSize); - const string text = "The quick brown fox jumps over the lazy dog"; - - SolidPen[] colors = - [ - new SolidPen(Color.Red), - new SolidPen(Color.Orange), - new SolidPen(Color.Yellow), - new SolidPen(Color.Green), - new SolidPen(Color.Blue), - new SolidPen(Color.Indigo), - new SolidPen(Color.Violet) - ]; - - List runs = []; - for (int i = 0; i < text.Length; i++) - { - SolidPen pen = colors[i % colors.Length]; - runs.Add(new RichTextRun - { - Start = i, - End = i + 1, - UnderlinePen = pen - }); - } - - RichTextOptions textOptions = new(font) - { - Origin = new Vector2(15), - WrappingLength = 400, - TextRuns = runs, - }; - - provider.RunValidatingProcessorTest( - x => x.DrawText(textOptions, text, Color.White), - $"RichText-Rainbow-F({fontSize})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithSolidFilledImages(100, 100, nameof(Color.Black), PixelTypes.Rgba32, "M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50", "spiral")] - [WithSolidFilledImages(350, 350, nameof(Color.Black), PixelTypes.Rgba32, "M275 175 A100 100 0 1 1 275 174", "circle")] - [WithSolidFilledImages(120, 120, nameof(Color.Black), PixelTypes.Rgba32, "M50,10 L 90 90 L 10 90 L50 10", "triangle")] - public void CanDrawRichTextAlongPathHorizontal(TestImageProvider provider, string svgPath, string exampleImageKey) - where TPixel : unmanaged, IPixel - { - bool parsed = Path.TryParseSvgPath(svgPath, out IPath path); - Assert.True(parsed); - - Font font = CreateFont(TestFonts.OpenSans, 13); - - const string text = "Quick brown fox jumps over the lazy dog."; - RichTextRun run = new() - { - Start = 0, - End = text.GetGraphemeCount(), - StrikeoutPen = new SolidPen(Color.Red) - }; - - RichTextOptions textOptions = new(font) - { - WrappingLength = path.ComputeLength(), - VerticalAlignment = VerticalAlignment.Bottom, - HorizontalAlignment = HorizontalAlignment.Left, - Path = path, - TextRuns = [run] - }; - - provider.RunValidatingProcessorTest( - x => x.DrawText(textOptions, text, Color.White), - $"RichText-Path-({exampleImageKey})", - TextDrawingComparer, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); - } - - [Theory] - [WithBlankImage(100, 100, PixelTypes.Rgba32, "M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50", "spiral")] - [WithBlankImage(350, 350, PixelTypes.Rgba32, "M275 175 A100 100 0 1 1 275 174", "circle")] - [WithBlankImage(120, 120, PixelTypes.Rgba32, "M50,10 L 90 90 L 10 90 L50 10", "triangle")] - public void CanDrawTextAlongPathHorizontal(TestImageProvider provider, string svgPath, string exampleImageKey) - where TPixel : unmanaged, IPixel - { - bool parsed = Path.TryParseSvgPath(svgPath, out IPath path); - Assert.True(parsed); - - const string text = "Quick brown fox jumps over the lazy dog."; - - Font font = CreateFont(TestFonts.OpenSans, 13); - RichTextOptions textOptions = new(font) - { - WrappingLength = path.ComputeLength(), - VerticalAlignment = VerticalAlignment.Bottom, - HorizontalAlignment = HorizontalAlignment.Left, - TextRuns = [new RichTextRun { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Strikeout }], - }; - - IPathCollection glyphs = TextBuilder.GeneratePaths(text, path, textOptions); - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).Draw(Color.Red, 1, path).Fill(Color.Black, glyphs), - new { type = exampleImageKey }, - comparer: ImageComparer.TolerantPercentage(0.0025f)); - } - - [Theory] - [WithBlankImage(350, 350, PixelTypes.Rgba32, "M225 175 A50 50 0 1 1 225 174", "circle")] - [WithBlankImage(250, 250, PixelTypes.Rgba32, "M100,60 L 140 140 L 60 140 L100 60", "triangle")] - public void CanDrawTextAlongPathVertical(TestImageProvider provider, string svgPath, string exampleImageKey) - where TPixel : unmanaged, IPixel - { - bool parsed = Path.TryParseSvgPath(svgPath, out IPath path); - Assert.True(parsed); - - Font font = CreateFont(TestFonts.OpenSans, 13); - RichTextOptions textOptions = new(font) - { - WrappingLength = path.ComputeLength() / 4, - VerticalAlignment = VerticalAlignment.Bottom, - HorizontalAlignment = HorizontalAlignment.Left, - LayoutMode = LayoutMode.VerticalLeftRight - }; - - const string text = "Quick brown fox jumps over the lazy dog."; - IPathCollection glyphs = TextBuilder.GeneratePaths(text, path, textOptions); - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).Draw(Color.Red, 1, path).Fill(Color.Black, glyphs), - new { type = exampleImageKey }, - comparer: ImageComparer.TolerantPercentage(0.002f)); - } - - [Theory] - [WithSolidFilledImages(1000, 1000, "White", PixelTypes.Rgba32)] - public void PathAndTextDrawingMatch(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // https://github.com/SixLabors/ImageSharp.Drawing/issues/234 - Font font = CreateFont(TestFonts.NettoOffc, 300); - const string text = "all"; - - provider.VerifyOperation( - TextDrawingComparer, - img => - { - foreach (HorizontalAlignment ha in (HorizontalAlignment[])Enum.GetValues(typeof(HorizontalAlignment))) - { - foreach (VerticalAlignment va in (VerticalAlignment[])Enum.GetValues(typeof(VerticalAlignment))) - { - TextOptions to = new(font) - { - HorizontalAlignment = ha, - VerticalAlignment = va, - }; - - FontRectangle bounds = TextMeasurer.MeasureBounds(text, to); - float x = (img.Size.Width - bounds.Width) / 2; - PointF[] pathLine = - [ - new PointF(x, 500), - new PointF(x + bounds.Width, 500) - ]; - - IPath path = new PathBuilder().AddLine(pathLine[0], pathLine[1]).Build(); - - RichTextOptions rto = new(font) - { - Origin = pathLine[0], - HorizontalAlignment = ha, - VerticalAlignment = va, - }; - - IPathCollection tb = TextBuilder.GeneratePaths(text, path, to); - - img.Mutate( - i => i.DrawLine(new SolidPen(Color.Red, 30), pathLine) - .DrawText(rto, text, Color.Black) - .Fill(Brushes.ForwardDiagonal(Color.HotPink), tb)); - } - } - }); - } - - [Theory] - [WithBlankImage(500, 400, PixelTypes.Rgba32)] - public void CanFillTextVertical(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 36); - Font fallback = CreateFont(TestFonts.NotoSansKRRegular, 36); - - const string text = "한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo"; - RichTextOptions textOptions = new(font) - { - Origin = new Vector2(0, 0), - FallbackFontFamilies = [fallback.Family], - WrappingLength = 300, - LayoutMode = LayoutMode.VerticalLeftRight, - TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline } - ] - }; - - IReadOnlyList glyphs = TextBuilder.GenerateGlyphs(text, textOptions); - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).Fill(Color.Black, glyphs), - comparer: ImageComparer.TolerantPercentage(0.002f)); - } - - [Theory] - [WithBlankImage(500, 400, PixelTypes.Rgba32)] - public void CanFillTextVerticalMixed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 36); - Font fallback = CreateFont(TestFonts.NotoSansKRRegular, 36); - - const string text = "한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo"; - RichTextOptions textOptions = new(font) - { - FallbackFontFamilies = [fallback.Family], - WrappingLength = 400, - LayoutMode = LayoutMode.VerticalMixedLeftRight, - TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline } - ] - }; - - IPathCollection glyphs = TextBuilder.GeneratePaths(text, textOptions); - - DrawingOptions options = new() { ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.NonZero } }; - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).Fill(options, Color.Black, glyphs), - comparer: ImageComparer.TolerantPercentage(0.002f)); - } - - [Theory] - [WithBlankImage(500, 400, PixelTypes.Rgba32)] - public void CanDrawTextVertical(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 36); - Font fallback = CreateFont(TestFonts.NotoSansKRRegular, 36); - - const string text = "한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo"; - RichTextOptions textOptions = new(font) - { - FallbackFontFamilies = [fallback.Family], - WrappingLength = 400, - LayoutMode = LayoutMode.VerticalLeftRight, - LineSpacing = 1.4F, - TextRuns = [ - new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline } - ] - }; - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).DrawText(textOptions, text, Brushes.Solid(Color.Black)), - comparer: ImageComparer.TolerantPercentage(0.002f)); - } - - [Theory] - [WithBlankImage(48, 935, PixelTypes.Rgba32)] - public void CanDrawTextVertical2(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (SystemFonts.TryGet("Yu Gothic", out FontFamily fontFamily)) - { - Font font = fontFamily.CreateFont(30F); - const string text = "あいうえお、「こんにちはー」。もしもし。ABCDEFG 日本語"; - RichTextOptions textOptions = new(font) - { - LayoutMode = LayoutMode.VerticalLeftRight, - LineSpacing = 1.4F, - TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline }] - }; - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).DrawText(textOptions, text, Brushes.Solid(Color.Black)), - comparer: ImageComparer.TolerantPercentage(0.002f)); - } - } - - [Theory] - [WithBlankImage(500, 400, PixelTypes.Rgba32)] - public void CanDrawTextVerticalMixed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Font font = CreateFont(TestFonts.OpenSans, 36); - Font fallback = CreateFont(TestFonts.NotoSansKRRegular, 36); - - const string text = "한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo"; - RichTextOptions textOptions = new(font) - { - FallbackFontFamilies = [fallback.Family], - WrappingLength = 400, - LayoutMode = LayoutMode.VerticalMixedLeftRight, - LineSpacing = 1.4F, - TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline }] - }; - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).DrawText(textOptions, text, Brushes.Solid(Color.Black)), - comparer: ImageComparer.TolerantPercentage(0.002f)); - } - - [Theory] - [WithBlankImage(48, 839, PixelTypes.Rgba32)] - public void CanDrawTextVerticalMixed2(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (SystemFonts.TryGet("Yu Gothic", out FontFamily fontFamily)) - { - Font font = fontFamily.CreateFont(30F); - const string text = "あいうえお、「こんにちはー」。もしもし。ABCDEFG 日本語"; - RichTextOptions textOptions = new(font) - { - LayoutMode = LayoutMode.VerticalMixedLeftRight, - LineSpacing = 1.4F, - TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline } - ] - }; - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).DrawText(textOptions, text, Brushes.Solid(Color.Black)), - comparer: ImageComparer.TolerantPercentage(0.002f)); - } - } - - [Theory] - [WithBlankImage(200, 200, PixelTypes.Rgba32)] - public void CanRenderTextOutOfBoundsIssue301(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - ImageComparer.TolerantPercentage(0.01f), - img => - { - Font font = CreateFont(TestFonts.OpenSans, 70); - - const string txt = "V"; - FontRectangle size = TextMeasurer.MeasureBounds(txt, new TextOptions(font)); - - img.Mutate(x => x.Resize((int)size.Width, (int)size.Height)); - - RichTextOptions options = new(font) - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - Origin = new Vector2(size.Width / 2, size.Height / 2) - }; - - LinearGradientBrush brush = new( - new PointF(0, 0), - new PointF(20, 20), - GradientRepetitionMode.Repeat, - new ColorStop(0, Color.Red), - new ColorStop(0.5f, Color.Green), - new ColorStop(0.5f, Color.Yellow), - new ColorStop(1f, Color.Blue)); - - img.Mutate(m => m.DrawText(options, txt, brush)); - }, - false, - false); - - private static string Repeat(string str, int times) => string.Concat(Enumerable.Repeat(str, times)); - - private static string ToTestOutputDisplayText(string text) - { - string fnDisplayText = text.Replace("\n", string.Empty); - return fnDisplayText[..Math.Min(fnDisplayText.Length, 4)]; - } - - private static Font CreateFont(string fontName, float size) - => TestFontUtilities.GetFont(fontName, size); -} diff --git a/tests/ImageSharp.Drawing.Tests/GraphicsOptionsTests.cs b/tests/ImageSharp.Drawing.Tests/GraphicsOptionsTests.cs index 6700b36d9..685fe348c 100644 --- a/tests/ImageSharp.Drawing.Tests/GraphicsOptionsTests.cs +++ b/tests/ImageSharp.Drawing.Tests/GraphicsOptionsTests.cs @@ -13,7 +13,7 @@ public class GraphicsOptionsTests private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); [Fact] - public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null); + public void CloneGraphicsOptionsIsNotNull() => Assert.NotNull(this.cloneGraphicsOptions); [Fact] public void DefaultGraphicsOptionsAntialias() @@ -25,25 +25,33 @@ public void DefaultGraphicsOptionsAntialias() [Fact] public void DefaultGraphicsOptionsBlendPercentage() { - const float Expected = 1F; - Assert.Equal(Expected, this.newGraphicsOptions.BlendPercentage); - Assert.Equal(Expected, this.cloneGraphicsOptions.BlendPercentage); + const float expected = 1F; + Assert.Equal(expected, this.newGraphicsOptions.BlendPercentage); + Assert.Equal(expected, this.cloneGraphicsOptions.BlendPercentage); } [Fact] public void DefaultGraphicsOptionsColorBlendingMode() { - const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal; - Assert.Equal(Expected, this.newGraphicsOptions.ColorBlendingMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.ColorBlendingMode); + const PixelColorBlendingMode expected = PixelColorBlendingMode.Normal; + Assert.Equal(expected, this.newGraphicsOptions.ColorBlendingMode); + Assert.Equal(expected, this.cloneGraphicsOptions.ColorBlendingMode); + } + + [Fact] + public void DefaultGraphicsOptionsAntialiasThreshold() + { + const float expected = 0.5F; + Assert.Equal(expected, this.newGraphicsOptions.AntialiasThreshold); + Assert.Equal(expected, this.cloneGraphicsOptions.AntialiasThreshold); } [Fact] public void DefaultGraphicsOptionsAlphaCompositionMode() { - const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver; - Assert.Equal(Expected, this.newGraphicsOptions.AlphaCompositionMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.AlphaCompositionMode); + const PixelAlphaCompositionMode expected = PixelAlphaCompositionMode.SrcOver; + Assert.Equal(expected, this.newGraphicsOptions.AlphaCompositionMode); + Assert.Equal(expected, this.cloneGraphicsOptions.AlphaCompositionMode); } [Fact] @@ -53,6 +61,7 @@ public void NonDefaultClone() { AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop, Antialias = false, + AntialiasThreshold = .25F, BlendPercentage = .25F, ColorBlendingMode = PixelColorBlendingMode.HardLight, }; @@ -70,19 +79,10 @@ public void CloneIsDeep() actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop; actual.Antialias = false; + actual.AntialiasThreshold = .25F; actual.BlendPercentage = .25F; actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; Assert.NotEqual(expected, actual, GraphicsOptionsComparer); } - - [Fact] - public void IsOpaqueColor() - { - Assert.True(new GraphicsOptions().IsOpaqueColorWithoutBlending(Color.Red)); - Assert.False(new GraphicsOptions { BlendPercentage = .5F }.IsOpaqueColorWithoutBlending(Color.Red)); - Assert.False(new GraphicsOptions().IsOpaqueColorWithoutBlending(Color.Transparent)); - Assert.False(new GraphicsOptions { ColorBlendingMode = PixelColorBlendingMode.Lighten, BlendPercentage = 1F }.IsOpaqueColorWithoutBlending(Color.Red)); - Assert.False(new GraphicsOptions { ColorBlendingMode = PixelColorBlendingMode.Normal, AlphaCompositionMode = PixelAlphaCompositionMode.DestOver, BlendPercentage = 1f }.IsOpaqueColorWithoutBlending(Color.Red)); - } } diff --git a/tests/ImageSharp.Drawing.Tests/Helpers/PolygonUtilitiesTests.cs b/tests/ImageSharp.Drawing.Tests/Helpers/PolygonUtilitiesTests.cs new file mode 100644 index 000000000..7c2aa4f20 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Helpers/PolygonUtilitiesTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing; +using SixLabors.ImageSharp.Drawing.Helpers; + +namespace SixLabors.ImageSharp.Drawing.Tests.Helpers; + +public class PolygonUtilitiesTests +{ + private static PointF[] CreateTestPoints() + => PolygonFactory.CreatePointArray( + (10, 0), + (20, 0), + (20, 30), + (10, 30), + (10, 20), + (0, 20), + (0, 10), + (10, 10), + (10, 0)); + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void EnsureOrientation_Positive(bool isPositive) + { + PointF[] expected = CreateTestPoints(); + PointF[] polygon = expected.CloneArray(); + + if (!isPositive) + { + polygon.AsSpan().Reverse(); + } + + PolygonUtilities.EnsureOrientation(polygon, 1); + + Assert.Equal(expected, polygon); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void EnsureOrientation_Negative(bool isNegative) + { + PointF[] expected = CreateTestPoints(); + expected.AsSpan().Reverse(); + + PointF[] polygon = expected.CloneArray(); + + if (!isNegative) + { + polygon.AsSpan().Reverse(); + } + + PolygonUtilities.EnsureOrientation(polygon, -1); + + Assert.Equal(expected, polygon); + } + + public static TheoryData<(float X, float Y), (float X, float Y), (float X, float Y), (float X, float Y), (float X, float Y)?> LineSegmentToLineSegment_Data { get; } = + new() + { + { (0, 0), (2, 3), (1, 3), (1, 0), (1, 1.5f) }, + { (3, 1), (3, 3), (3, 2), (4, 2), (3, 2) }, + { (1, -3), (3, -1), (3, -4), (2, -2), (2, -2) }, + { (0, 0), (2, 1), (2, 1.0001f), (5, 2), (2, 1) }, // Robust to inaccuracies + { (0, 0), (2, 3), (1, 3), (1, 2), null }, + { (-3, 3), (-1, 3), (-3, 2), (-1, 2), null }, + { (-4, 3), (-4, 1), (-5, 3), (-5, 1), null }, + { (0, 0), (4, 1), (4, 1), (8, 2), null }, // Collinear intersections are ignored + { (0, 0), (4, 1), (4, 1.0001f), (8, 2), null }, // Collinear intersections are ignored + }; + + [Theory] + [MemberData(nameof(LineSegmentToLineSegment_Data))] + public void LineSegmentToLineSegmentNoCollinear( + (float X, float Y) a0, + (float X, float Y) a1, + (float X, float Y) b0, + (float X, float Y) b1, + (float X, float Y)? expected) + { + Vector2 ip = default; + + bool result = PolygonUtilities.LineSegmentToLineSegmentIgnoreCollinear(P(a0), P(a1), P(b0), P(b1), ref ip); + Assert.Equal(result, expected.HasValue); + if (expected.HasValue) + { + Assert.Equal(P(expected.Value), ip, new ApproximateFloatComparer(1e-3f)); + } + + static Vector2 P((float X, float Y) p) => new(p.X, p.Y); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Utilities/ThreadLocalBlenderBuffersTests.cs b/tests/ImageSharp.Drawing.Tests/Helpers/ThreadLocalBlenderBuffersTests.cs similarity index 94% rename from tests/ImageSharp.Drawing.Tests/Utilities/ThreadLocalBlenderBuffersTests.cs rename to tests/ImageSharp.Drawing.Tests/Helpers/ThreadLocalBlenderBuffersTests.cs index 42d7e5983..c21cd931a 100644 --- a/tests/ImageSharp.Drawing.Tests/Utilities/ThreadLocalBlenderBuffersTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Helpers/ThreadLocalBlenderBuffersTests.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Drawing.Utilities; +using SixLabors.ImageSharp.Drawing.Helpers; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Utils; +namespace SixLabors.ImageSharp.Drawing.Tests.Helpers; public class ThreadLocalBlenderBuffersTests { diff --git a/tests/ImageSharp.Drawing.Tests/ImageSharp.Drawing.Tests.csproj b/tests/ImageSharp.Drawing.Tests/ImageSharp.Drawing.Tests.csproj index a7b7f0564..7a587c277 100644 --- a/tests/ImageSharp.Drawing.Tests/ImageSharp.Drawing.Tests.csproj +++ b/tests/ImageSharp.Drawing.Tests/ImageSharp.Drawing.Tests.csproj @@ -24,15 +24,17 @@ - - - + + + + + - + @@ -51,8 +53,17 @@ + + + + + + + + + + - diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_134.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_134.cs new file mode 100644 index 000000000..9584a1561 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_134.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Issues; + +public class Issue_134 +{ + [Theory] + [WithSolidFilledImages(128, 64, nameof(Color.White), PixelTypes.Rgba32, true)] + [WithSolidFilledImages(128, 64, nameof(Color.White), PixelTypes.Rgba32, false)] + public void LowFontSizeRenderOK(TestImageProvider provider, bool antialias) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.IsWindows) + { + return; + } + + provider.RunValidatingProcessorTest( + c => + { + c.SetGraphicsOptions( + new GraphicsOptions + { + Antialias = antialias, + AntialiasThreshold = .33F + }); + + c.ProcessWithCanvas(canvas => + { + Brush brush = Brushes.Solid(Color.Black); + Font font = SystemFonts.Get("Tahoma").CreateFont(8); + RichTextOptions options = new(font) + { + WrappingLength = c.GetCurrentSize().Width / 2, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + Origin = new PointF(c.GetCurrentSize().Width / 2, c.GetCurrentSize().Height / 2) + }; + + canvas.DrawText(options, "Lorem ipsum dolor sit amet", brush, null); + }); + }, + testOutputDetails: $"{antialias}", + appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_19.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_19.cs similarity index 96% rename from tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_19.cs rename to tests/ImageSharp.Drawing.Tests/Issues/Issue_19.cs index d920fa9d0..21fbd5784 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_19.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_19.cs @@ -3,7 +3,7 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Drawing.Tests; +namespace SixLabors.ImageSharp.Drawing.Tests.Issues; /// /// see https://github.com/issues/19 diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_224.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_224.cs similarity index 97% rename from tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_224.cs rename to tests/ImageSharp.Drawing.Tests/Issues/Issue_224.cs index aa9391361..c682f1935 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Issues/Issue_224.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_224.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Drawing.Tests; +namespace SixLabors.ImageSharp.Drawing.Tests.Issues; /// /// see https://github.com/SixLabors/ImageSharp.Drawing/issues/224 diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_241.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_241.cs index 1d208a0da..a29b32676 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_241.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_241.cs @@ -27,6 +27,6 @@ public void DoesNotThrowArgumentOutOfRangeException() const string content = "TEST"; using Image image = new Image(512, 256, Color.Black.ToPixel()); - image.Mutate(x => x.DrawText(opt, content, Brushes.Horizontal(Color.Orange))); + image.Mutate(x => x.ProcessWithCanvas(canvas => canvas.DrawText(opt, content, Brushes.Horizontal(Color.Orange), pen: null))); } } diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_244.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_244.cs new file mode 100644 index 000000000..1375e254b --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_244.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.Drawing.Tests.Issues; + +public class Issue_244 +{ + [Fact] + public void DoesNotHang() + { + PathBuilder pathBuilder = new(); + Matrix4x4 transform = new(Matrix3x2.CreateRotation(-0.04433158f, new Vector2(948, 640))); + pathBuilder.SetTransform(transform); + pathBuilder.AddQuadraticBezier(new PointF(-2147483648, 677), new PointF(-2147483648, 675), new PointF(-2147483648, 675)); + IPath path = pathBuilder.Build(); + + IPath outline = path.GenerateOutline(2); + + Assert.NotEqual(Rectangle.Empty, outline.Bounds); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_270.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_270.cs index 43ad525be..67f3c52f3 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_270.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_270.cs @@ -33,7 +33,7 @@ public void DoesNotThrowArgumentOutOfRangeException() using Image imageBrushImage = new(sourceImageWidth, sourceImageHeight, Color.Black.ToPixel()); ImageBrush imageBrush = new(imageBrushImage); - targetImage.Mutate(x => x.DrawText(CreateTextOptions(font, targetImageWidth), text, imageBrush, pen)); + targetImage.Mutate(x => x.ProcessWithCanvas(canvas => canvas.DrawText(CreateTextOptions(font, targetImageWidth), text, imageBrush, pen))); } private static RichTextOptions CreateTextOptions(Font font, int wrappingLength) diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_28_108.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_28_108.cs index d18810746..f05526c45 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_28_108.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_28_108.cs @@ -9,8 +9,6 @@ namespace SixLabors.ImageSharp.Drawing.Tests.Issues; public class Issue_28_108 { - private Rgba32 red = Color.Red.ToPixel(); - [Theory] [InlineData(1F)] [InlineData(1.5F)] @@ -19,13 +17,13 @@ public class Issue_28_108 public void DrawingLineAtTopShouldDisplay(float stroke) { using Image image = new(Configuration.Default, 100, 100, Color.Black.ToPixel()); - image.Mutate(x => x - .SetGraphicsOptions(g => g.Antialias = false) - .DrawLine( - Color.Red, - stroke, - new PointF(0, 0), - new PointF(100, 0))); + DrawingOptions options = CreateAliasedDrawingOptions(); + image.Mutate(x => x.ProcessWithCanvas( + options, + canvas => canvas.DrawLine( + Pens.Solid(Color.Red, stroke), + new PointF(0, 0), + new PointF(100, 0)))); IEnumerable<(int X, int Y)> locations = Enumerable.Range(0, 100).Select(i => (x: i, y: 0)); Assert.All(locations, l => Assert.Equal(Color.Red.ToPixel(), image[l.X, l.Y])); @@ -39,13 +37,13 @@ public void DrawingLineAtTopShouldDisplay(float stroke) public void DrawingLineAtBottomShouldDisplay(float stroke) { using Image image = new(Configuration.Default, 100, 100, Color.Black.ToPixel()); - image.Mutate(x => x - .SetGraphicsOptions(g => g.Antialias = false) - .DrawLine( - Color.Red, - stroke, - new PointF(0, 99), - new PointF(100, 99))); + DrawingOptions options = CreateAliasedDrawingOptions(); + image.Mutate(x => x.ProcessWithCanvas( + options, + canvas => canvas.DrawLine( + Pens.Solid(Color.Red, stroke), + new PointF(0, 99), + new PointF(100, 99)))); IEnumerable<(int X, int Y)> locations = Enumerable.Range(0, 100).Select(i => (x: i, y: 99)); Assert.All(locations, l => Assert.Equal(Color.Red.ToPixel(), image[l.X, l.Y])); @@ -59,13 +57,13 @@ public void DrawingLineAtBottomShouldDisplay(float stroke) public void DrawingLineAtLeftShouldDisplay(float stroke) { using Image image = new(Configuration.Default, 100, 100, Color.Black.ToPixel()); - image.Mutate(x => x - .SetGraphicsOptions(g => g.Antialias = false) - .DrawLine( - Color.Red, - stroke, - new PointF(0, 0), - new PointF(0, 99))); + DrawingOptions options = CreateAliasedDrawingOptions(); + image.Mutate(x => x.ProcessWithCanvas( + options, + canvas => canvas.DrawLine( + Pens.Solid(Color.Red, stroke), + new PointF(0, 0), + new PointF(0, 99)))); IEnumerable<(int X, int Y)> locations = Enumerable.Range(0, 100).Select(i => (x: 0, y: i)); Assert.All(locations, l => Assert.Equal(Color.Red.ToPixel(), image[l.X, l.Y])); @@ -79,15 +77,24 @@ public void DrawingLineAtLeftShouldDisplay(float stroke) public void DrawingLineAtRightShouldDisplay(float stroke) { using Image image = new(Configuration.Default, 100, 100, Color.Black.ToPixel()); - image.Mutate(x => x - .SetGraphicsOptions(g => g.Antialias = false) - .DrawLine( - Color.Red, - stroke, - new PointF(99, 0), - new PointF(99, 99))); + DrawingOptions options = CreateAliasedDrawingOptions(); + image.Mutate(x => x.ProcessWithCanvas( + options, + canvas => canvas.DrawLine( + Pens.Solid(Color.Red, stroke), + new PointF(99, 0), + new PointF(99, 99)))); IEnumerable<(int X, int Y)> locations = Enumerable.Range(0, 100).Select(i => (x: 99, y: i)); Assert.All(locations, l => Assert.Equal(Color.Red.ToPixel(), image[l.X, l.Y])); } + + private static DrawingOptions CreateAliasedDrawingOptions() => + new() + { + GraphicsOptions = new GraphicsOptions + { + Antialias = false + } + }; } diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_323.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_323.cs index c77648fe9..e605ca4f2 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_323.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_323.cs @@ -18,15 +18,15 @@ public void DrawPolygonMustDrawoutlineOnly(TestImageProvider pro where TPixel : unmanaged, IPixel { Color color = Color.RebeccaPurple; + PointF[] points = + [ + new(5, 5), + new(5, 150), + new(190, 150), + ]; + provider.RunValidatingProcessorTest( - x => x.DrawPolygon( - color, - scale, - [ - new(5, 5), - new(5, 150), - new(190, 150), - ]), + x => x.ProcessWithCanvas(canvas => canvas.Draw(Pens.Solid(color, scale), new Polygon(points))), new { scale }); } @@ -40,15 +40,16 @@ public void DrawPolygonMustDrawoutlineOnly_Pattern(TestImageProvider { Color color = Color.RebeccaPurple; + PointF[] points = + [ + new(5, 5), + new(5, 150), + new(190, 150), + ]; + PatternPen pen = Pens.DashDot(color, scale); provider.RunValidatingProcessorTest( - x => x.DrawPolygon( - pen, - [ - new(5, 5), - new(5, 150), - new(190, 150), - ]), - new { scale }); + x => x.ProcessWithCanvas(canvas => canvas.Draw(pen, new Polygon(points))), + new { scale }); } } diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_330.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_330.cs index 26e151ddd..3fb1e24c5 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_330.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_330.cs @@ -19,46 +19,46 @@ public void OffsetTextOutlines(TestImageProvider provider) Font bibfont = fontFamily.CreateFont(600, FontStyle.Bold); Font namefont = fontFamily.CreateFont(140, FontStyle.Bold); - provider.RunValidatingProcessorTest(p => - { - p.DrawText( - new RichTextOptions(bibfont) - { - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - TextAlignment = TextAlignment.Center, - TextDirection = TextDirection.LeftToRight, - Origin = new Point(1156, 1024), - }, - "9999", - Brushes.Solid(Color.White), - Pens.Solid(Color.Black, 20)); + provider.RunValidatingProcessorTest(p => p.ProcessWithCanvas(canvas => + { + canvas.DrawText( + new RichTextOptions(bibfont) + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + TextAlignment = TextAlignment.Center, + TextDirection = TextDirection.LeftToRight, + Origin = new Point(1156, 1024), + }, + "9999", + Brushes.Solid(Color.White), + Pens.Solid(Color.Black, 20)); - p.DrawText( - new RichTextOptions(namefont) - { - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - TextAlignment = TextAlignment.Center, - TextDirection = TextDirection.LeftToRight, - Origin = new Point(1156, 713), - }, - "JOHAN", - Brushes.Solid(Color.White), - Pens.Solid(Color.Black, 5)); + canvas.DrawText( + new RichTextOptions(namefont) + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + TextAlignment = TextAlignment.Center, + TextDirection = TextDirection.LeftToRight, + Origin = new Point(1156, 713), + }, + "JOHAN", + Brushes.Solid(Color.White), + Pens.Solid(Color.Black, 5)); - p.DrawText( - new RichTextOptions(namefont) - { - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - TextAlignment = TextAlignment.Center, - TextDirection = TextDirection.LeftToRight, - Origin = new Point(1156, 1381), - }, - "TIGERTECH", - Brushes.Solid(Color.White), - Pens.Solid(Color.Black, 5)); - }); + canvas.DrawText( + new RichTextOptions(namefont) + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + TextAlignment = TextAlignment.Center, + TextDirection = TextDirection.LeftToRight, + Origin = new Point(1156, 1381), + }, + "TIGERTECH", + Brushes.Solid(Color.White), + Pens.Solid(Color.Black, 5)); + })); } } diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_344.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_344.cs new file mode 100644 index 000000000..697cf3db0 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_344.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Issues; + +public class Issue_344 +{ + [Theory] + [WithSolidFilledImages(100, 100, nameof(Color.Black), PixelTypes.Rgba32)] + public void CanDrawWhereSegmentsOverlap(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + Pen pen = Pens.Solid(Color.Aqua.WithAlpha(.3F), 1); + canvas.DrawLine(pen, new PointF(10, 10), new PointF(90, 10), new PointF(20, 10)); + })); + + [Theory] + [WithSolidFilledImages(100, 100, nameof(Color.Black), PixelTypes.Rgba32)] + public void CanDrawWhereSegmentsOverlap_PathBuilder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + PathBuilder pathBuilder = new(); + pathBuilder.MoveTo(10, 10); + pathBuilder.LineTo(90, 10); + pathBuilder.LineTo(20, 10); + + Pen pen = Pens.Solid(Color.Aqua.WithAlpha(.3F), 1); + canvas.Draw(pen, pathBuilder); + })); +} diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_367.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_367.cs new file mode 100644 index 000000000..46bf3624f --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_367.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Issues; + +public class Issue_367 +{ + [Theory] + [WithSolidFilledImages(512, 72, nameof(Color.White), PixelTypes.Rgba32)] + public void BrushAndTextAlign(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.IsWindows) + { + return; + } + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + Pen pen = Pens.Solid(Color.Green, 1); + Brush brush = Brushes.Solid(Color.Red); + + Font font = SystemFonts.Get("Arial").CreateFont(64); + RichTextOptions options = new(font); + + canvas.DrawText(options, "Hello, world!", brush, pen); + }), + appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_37.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_37.cs index 31e156033..0748409c8 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_37.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_37.cs @@ -23,20 +23,20 @@ public void CanRenderLargeFont() Fonts.Font font = Fonts.SystemFonts.CreateFont("Arial", 40, Fonts.FontStyle.Regular); GraphicsOptions graphicsOptions = new() { Antialias = false }; + DrawingOptions drawingOptions = new() { GraphicsOptions = graphicsOptions }; + RichTextOptions textOptions = new(font) { Origin = new PointF(50, 50) }; image.Mutate( - x => x.BackgroundColor(Color.White) - .DrawLine( - new DrawingOptions { GraphicsOptions = graphicsOptions }, - Color.Black, - 1, - new PointF(0, 50), - new PointF(150, 50)) - .DrawText( - new DrawingOptions { GraphicsOptions = graphicsOptions }, - text, - font, - Color.Black, - new PointF(50, 50))); + x => x.ProcessWithCanvas( + drawingOptions, + canvas => + { + canvas.Clear(Brushes.Solid(Color.White)); + canvas.DrawLine( + Pens.Solid(Color.Black, 1), + new PointF(0, 50), + new PointF(150, 50)); + canvas.DrawText(textOptions, text, Brushes.Solid(Color.Black), pen: null); + })); } } } diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_46.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_46.cs index 41e6d410c..fe2f43322 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_46.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_46.cs @@ -32,7 +32,8 @@ public void CanRenderCustomFont() float textX = ((imageSize - rect.Width) * 0.5F) + rect.Left; float textY = ((imageSize - rect.Height) * 0.5F) + (rect.Top * 0.25F); - image.Mutate(x => x.DrawText(iconText, font, Color.Black, new PointF(textX, textY))); + RichTextOptions textOptions = new(font) { Origin = new PointF(textX, textY) }; + image.Mutate(x => x.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, iconText, Brushes.Solid(Color.Black), pen: null))); image.Save(TestFontUtilities.GetPath("e96.png")); } diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_462.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_462.cs index 378b0f53a..25087a427 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_462.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_462.cs @@ -42,7 +42,7 @@ public void CanDrawEmojiFont(TestImageProvider provider, ColorFo }; provider.RunValidatingProcessorTest( - c => c.DrawText(options, text, Brushes.Solid(Color.Black)), + c => c.ProcessWithCanvas(canvas => canvas.DrawText(options, text, Brushes.Solid(Color.Black), pen: null)), testOutputDetails: $"{support}-draw", comparer: ImageComparer.TolerantPercentage(0.002f)); @@ -50,7 +50,7 @@ public void CanDrawEmojiFont(TestImageProvider provider, ColorFo c => { Pen pen = Pens.Solid(Color.Black, 2); - c.Fill(pen.StrokeFill, pen, TextBuilder.GenerateGlyphs(text, options)); + c.ProcessWithCanvas(canvas => canvas.DrawGlyphs(pen.StrokeFill, pen, TextBuilder.GenerateGlyphs(text, options))); }, testOutputDetails: $"{support}-fill", comparer: ImageComparer.TolerantPercentage(0.002f)); diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issue_54.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issue_54.cs index 32e594f8b..7393b8616 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issue_54.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issue_54.cs @@ -37,7 +37,7 @@ public void CanDrawWithoutMemoryException() string text = "sample text"; // Draw the text - image.Mutate(x => x.DrawText(textOptions, text, brush, pen)); + image.Mutate(x => x.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, brush, pen))); } } diff --git a/tests/ImageSharp.Drawing.Tests/Issues/Issues_55_59.cs b/tests/ImageSharp.Drawing.Tests/Issues/Issues_55_59.cs index 1e25f7617..7aabda00c 100644 --- a/tests/ImageSharp.Drawing.Tests/Issues/Issues_55_59.cs +++ b/tests/ImageSharp.Drawing.Tests/Issues/Issues_55_59.cs @@ -24,7 +24,8 @@ public void SimplifyOutOfRangeExceptionDrawLines() ]; using Image image = new(100, 100); - image.Mutate(imageContext => imageContext.DrawLine(Color.FromPixel(new Rgba32(255, 0, 0)), 1, line)); + image.Mutate(imageContext => imageContext.ProcessWithCanvas( + canvas => canvas.DrawLine(Pens.Solid(Color.FromPixel(new Rgba32(255, 0, 0)), 1), line))); } [Fact] @@ -37,6 +38,7 @@ public void SimplifyOutOfRangeExceptionDraw() new LinearLineSegment(new PointF(592.916f, 1155.754f), new PointF(592.0153f, 1156.238f))); using Image image = new(2000, 2000); - image.Mutate(imageContext => imageContext.Draw(Color.FromPixel(new Rgba32(255, 0, 0)), 1, path)); + image.Mutate(imageContext => imageContext.ProcessWithCanvas( + canvas => canvas.Draw(Pens.Solid(Color.FromPixel(new Rgba32(255, 0, 0)), 1), path))); } } diff --git a/tests/ImageSharp.Drawing.Tests/PolygonGeometry/PolygonClippingTests.cs b/tests/ImageSharp.Drawing.Tests/PolygonGeometry/PolygonClippingTests.cs new file mode 100644 index 000000000..4e741462c --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/PolygonGeometry/PolygonClippingTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Drawing.PolygonGeometry; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Drawing.Tests.PolygonGeometry; + +public class PolygonClippingTests +{ + private readonly RectangularPolygon bigSquare = new(10, 10, 40, 40); + private readonly RectangularPolygon hole = new(20, 20, 10, 10); + private readonly RectangularPolygon topLeft = new(0, 0, 20, 20); + private readonly RectangularPolygon topRight = new(30, 0, 20, 20); + private readonly RectangularPolygon topMiddle = new(20, 0, 10, 20); + + private readonly Polygon bigTriangle = new(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + private readonly Polygon littleTriangle = new(new LinearLineSegment( + new Vector2(37, 85), + new Vector2(130, 40), + new Vector2(65, 137))); + + private static ComplexPolygon Clip(IPath shape, params IPath[] hole) + => ClippedShapeGenerator.GenerateClippedShapes(BooleanOperation.Difference, shape, hole); + + [Fact] + public void OverlappingTriangleCutRightSide() + { + Polygon triangle = new(new LinearLineSegment( + new Vector2(0, 50), + new Vector2(70, 0), + new Vector2(50, 100))); + + Polygon cutout = new(new LinearLineSegment( + new Vector2(20, 0), + new Vector2(70, 0), + new Vector2(70, 100), + new Vector2(20, 100))); + + ComplexPolygon shapes = Clip(triangle, cutout); + Assert.Single(shapes.Paths); + Assert.DoesNotContain(triangle, shapes.Paths); + } + + [Fact] + public void OverlappingTriangles() + { + ComplexPolygon shapes = Clip(this.bigTriangle, this.littleTriangle); + Assert.Single(shapes.Paths); + PointF[] path = shapes.Paths.Single().Flatten().First().Points.ToArray(); + + Assert.Equal(7, path.Length); + foreach (Vector2 p in this.bigTriangle.Flatten().First().Points.ToArray()) + { + Assert.Contains(p, path, new ApproximateFloatComparer(RectangularPolygonValueComparer.DefaultTolerance)); + } + } + + [Fact] + public void NonOverlapping() + { + IEnumerable shapes = Clip(this.topLeft, this.topRight).Paths + .OfType().Select(x => (RectangularPolygon)x); + + Assert.Single(shapes); + Assert.Contains( + shapes, x => RectangularPolygonValueComparer.Equals(this.topLeft, x)); + + Assert.DoesNotContain(this.topRight, shapes); + } + + [Fact] + public void OverLappingReturns1NewShape() + { + ComplexPolygon shapes = Clip(this.bigSquare, this.topLeft); + + Assert.Single(shapes.Paths); + Assert.DoesNotContain(shapes.Paths, x => RectangularPolygonValueComparer.Equals(this.bigSquare, x)); + Assert.DoesNotContain(shapes.Paths, x => RectangularPolygonValueComparer.Equals(this.topLeft, x)); + } + + [Fact] + public void OverlappingButNotCrossingReturnsOrigionalShapes() + { + IEnumerable shapes = Clip(this.bigSquare, this.hole).Paths + .OfType().Select(x => (RectangularPolygon)x); + + Assert.Equal(2, shapes.Count()); + + Assert.Contains(shapes, x => RectangularPolygonValueComparer.Equals(this.bigSquare, x)); + Assert.Contains(shapes, x => RectangularPolygonValueComparer.Equals(this.hole, x)); + } + + [Fact] + public void TouchingButNotOverlapping() + { + ComplexPolygon shapes = Clip(this.topMiddle, this.topLeft); + Assert.Single(shapes.Paths); + Assert.DoesNotContain(shapes.Paths, x => RectangularPolygonValueComparer.Equals(this.topMiddle, x)); + Assert.DoesNotContain(shapes.Paths, x => RectangularPolygonValueComparer.Equals(this.topLeft, x)); + } + + [Fact] + public void ClippingRectanglesCreateCorrectNumberOfPoints() + { + IEnumerable paths = new RectangularPolygon(10, 10, 40, 40) + .Clip(new RectangularPolygon(20, 0, 20, 20)) + .Flatten(); + + Assert.Single(paths); + PointF[] points = paths.First().Points.ToArray(); + + Assert.Equal(8, points.Length); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/Backends/WebGPUDrawingBackendTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/Backends/WebGPUDrawingBackendTests.cs new file mode 100644 index 000000000..4f8863522 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/Backends/WebGPUDrawingBackendTests.cs @@ -0,0 +1,2156 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Processing.Backends; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.Attributes; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing.Backends; + +[GroupOutput("Drawing")] +public partial class WebGPUDrawingBackendTests +{ + public static TheoryData GraphicsOptionsModePairs { get; } = + new() + { + { PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver }, + { PixelColorBlendingMode.Multiply, PixelAlphaCompositionMode.SrcAtop }, + { PixelColorBlendingMode.Add, PixelAlphaCompositionMode.Src }, + { PixelColorBlendingMode.Subtract, PixelAlphaCompositionMode.DestOut }, + { PixelColorBlendingMode.Screen, PixelAlphaCompositionMode.DestOver }, + { PixelColorBlendingMode.Darken, PixelAlphaCompositionMode.DestAtop }, + { PixelColorBlendingMode.Lighten, PixelAlphaCompositionMode.DestIn }, + { PixelColorBlendingMode.Overlay, PixelAlphaCompositionMode.SrcIn }, + { PixelColorBlendingMode.HardLight, PixelAlphaCompositionMode.Xor }, + { PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear } + }; + + [WebGPUTheory] + [WithSolidFilledImages(512, 512, "White", PixelTypes.Rgba32)] + public void FillPath_WithWebGPUCoverageBackend_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon polygon = new(48.25F, 63.5F, 401.25F, 302.75F); + Brush brush = Brushes.Solid(Color.Black); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, polygon); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath", defaultImage, nativeSurfaceImage); + + Assert.True(nativeSurfaceBackend.TestingPrepareCoverageCallCount > 0); + Assert.Equal(nativeSurfaceBackend.TestingPrepareCoverageCallCount, nativeSurfaceBackend.TestingReleaseCoverageCallCount); + Assert.Equal(0, nativeSurfaceBackend.TestingLiveCoverageCount); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(512, 512, "White", PixelTypes.Rgba32)] + public void FillPath_AliasedWithThreshold_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = false, AntialiasThreshold = 0.25F } + }; + + EllipsePolygon ellipse = new(256, 256, 200, 150); + Brush brush = Brushes.Solid(Color.Black); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, ellipse); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_AliasedThreshold", defaultImage, nativeSurfaceImage); + + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithBasicTestPatternImages(384, 256, PixelTypes.Rgba32)] + public void FillPath_WithImageBrush_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon polygon = new(36.5F, 26.25F, 312.5F, 188.5F); + Brush clearBrush = Brushes.Solid(Color.White); + + using Image foreground = provider.GetImage(); + Brush brush = new ImageBrush(foreground, new RectangleF(32, 24, 192, 144), new Point(13, -9)); + void DrawAction(DrawingCanvas canvas) + { + canvas.Clear(clearBrush); + canvas.Fill(brush, polygon); + } + + using Image defaultImage = new(384, 256); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + (Action>)DrawAction); + + DebugSaveBackendPair(provider, "FillPath_ImageBrush", defaultImage, nativeSurfaceImage); + + Assert.True(nativeSurfaceBackend.TestingPrepareCoverageCallCount > 0); + Assert.Equal(nativeSurfaceBackend.TestingPrepareCoverageCallCount, nativeSurfaceBackend.TestingReleaseCoverageCallCount); + Assert.Equal(0, nativeSurfaceBackend.TestingLiveCoverageCount); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + if (nativeSurfaceBackend.TestingIsGPUReady) + { + Assert.True(nativeSurfaceBackend.TestingGPUCompositeCoverageCallCount > 0); + } + + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithNonZeroNestedContours_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true }, + ShapeOptions = new ShapeOptions + { + IntersectionRule = IntersectionRule.NonZero + } + }; + + PathBuilder pathBuilder = new(); + pathBuilder.StartFigure(); + pathBuilder.AddLines( + [ + new PointF(16, 16), + new PointF(240, 16), + new PointF(240, 240), + new PointF(16, 240) + ]); + pathBuilder.CloseFigure(); + + // Inner contour keeps the same winding direction as outer contour. + // Non-zero fill should therefore keep this region filled. + pathBuilder.StartFigure(); + pathBuilder.AddLines( + [ + new PointF(80, 80), + new PointF(176, 80), + new PointF(176, 176), + new PointF(80, 176) + ]); + pathBuilder.CloseFigure(); + + IPath path = pathBuilder.Build(); + Brush brush = Brushes.Solid(Color.Black); + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, path); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_NonZeroNestedContours", defaultImage, nativeSurfaceImage); + + Assert.True(nativeSurfaceBackend.TestingPrepareCoverageCallCount > 0); + Assert.Equal(nativeSurfaceBackend.TestingPrepareCoverageCallCount, nativeSurfaceBackend.TestingReleaseCoverageCallCount); + Assert.Equal(0, nativeSurfaceBackend.TestingLiveCoverageCount); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + + AssertGpuPathWhenRequired(nativeSurfaceBackend); + + // Non-zero winding semantics must still match on an interior point. + Assert.Equal(defaultImage[128, 128], nativeSurfaceImage[128, 128]); + + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.5F); + } + + [WebGPUTheory] + [WithBasicTestPatternImages(nameof(GraphicsOptionsModePairs), 384, 256, PixelTypes.Rgba32)] + public void FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput( + TestImageProvider provider, + PixelColorBlendingMode colorMode, + PixelAlphaCompositionMode alphaMode) + where TPixel : unmanaged, IPixel + { + RectangularPolygon polygon = new(26.5F, 18.25F, 324.5F, 208.75F); + Brush brush = Brushes.Solid(Color.OrangeRed.WithAlpha(0.78F)); + + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions + { + Antialias = true, + BlendPercentage = 0.73F, + ColorBlendingMode = colorMode, + AlphaCompositionMode = alphaMode + } + }; + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, polygon); + + using Image baseImage = provider.GetImage(); + using Image defaultImage = baseImage.Clone(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + baseImage); + + DebugSaveBackendPair( + provider, + $"FillPath_GraphicsOptions_SolidBrush_{colorMode}_{alphaMode}", + defaultImage, + nativeSurfaceImage); + + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.1F); + } + + [WebGPUTheory] + [WithBasicTestPatternImages(nameof(GraphicsOptionsModePairs), 384, 256, PixelTypes.Rgba32)] + public void FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput( + TestImageProvider provider, + PixelColorBlendingMode colorMode, + PixelAlphaCompositionMode alphaMode) + where TPixel : unmanaged, IPixel + { + RectangularPolygon polygon = new(26.5F, 18.25F, 324.5F, 208.75F); + + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions + { + Antialias = true, + BlendPercentage = 0.73F, + ColorBlendingMode = colorMode, + AlphaCompositionMode = alphaMode + } + }; + + using Image foreground = provider.GetImage(); + Brush brush = new ImageBrush(foreground, new RectangleF(32, 24, 192, 144), new Point(13, -9)); + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, polygon); + + using Image baseImage = provider.GetImage(); + using Image defaultImage = baseImage.Clone(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + baseImage); + + DebugSaveBackendPair( + provider, + $"FillPath_GraphicsOptions_ImageBrush_{colorMode}_{alphaMode}", + defaultImage, + nativeSurfaceImage); + + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(1200, 280, "White", PixelTypes.Rgba32)] + public void DrawText_WithWebGPUCoverageBackend_RendersAndReleasesPreparedCoverage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 54); + RichTextOptions textOptions = new(font) + { + Origin = new PointF(18, 28) + }; + + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + string text = "Sphinx of black quartz, judge my vow\n0123456789"; + Brush brush = Brushes.Solid(Color.Black); + Pen pen = Pens.Solid(Color.OrangeRed, 2F); + void DrawAction(DrawingCanvas canvas) => canvas.DrawText(textOptions, text, brush, pen); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "DrawText", defaultImage, nativeSurfaceImage); + + Assert.True(nativeSurfaceBackend.TestingPrepareCoverageCallCount > 0); + Assert.True(nativeSurfaceBackend.TestingCompositeCoverageCallCount >= nativeSurfaceBackend.TestingPrepareCoverageCallCount); + Assert.Equal(nativeSurfaceBackend.TestingPrepareCoverageCallCount, nativeSurfaceBackend.TestingReleaseCoverageCallCount); + Assert.Equal(0, nativeSurfaceBackend.TestingLiveCoverageCount); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + + AssertGpuPathWhenRequired(nativeSurfaceBackend); + + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.007F); + Rectangle textRegion = Rectangle.Intersect( + new Rectangle(0, 0, defaultImage.Width, defaultImage.Height), + new Rectangle(8, 12, defaultImage.Width - 16, Math.Min(220, defaultImage.Height - 12))); + AssertBackendPairSimilarityInRegion(defaultImage, nativeSurfaceImage, textRegion, 0.009F); + } + + [WebGPUTheory] + [WithSolidFilledImages(512, 512, "White", PixelTypes.Rgba32)] + public void FillPath_WithWebGPUCoverageBackend_NativeSurface_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon polygon = new(48.25F, 63.5F, 401.25F, 302.75F); + Brush brush = Brushes.Solid(Color.Black); + Brush clearBrush = Brushes.Solid(Color.White); + void DrawAction(DrawingCanvas canvas) + { + canvas.Clear(clearBrush); + canvas.Fill(brush, polygon); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_NativeSurfaceParity", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.5F); + } + + [WebGPUTheory] + [WithSolidFilledImages(512, 512, "White", PixelTypes.Rgba32)] + public void FillPath_WithWebGPUCoverageBackend_NativeSurfaceSubregion_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + Rectangle region = new(72, 64, 320, 240); + RectangularPolygon localPolygon = new(16.25F, 24.5F, 250.5F, 160.75F); + Brush brush = Brushes.Solid(Color.Black); + Brush clearBrush = Brushes.Solid(Color.White); + void DrawAction(DrawingCanvas canvas) + { + canvas.Clear(clearBrush); + + using DrawingCanvas regionCanvas = canvas.CreateRegion(region); + regionCanvas.Fill(brush, localPolygon); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_NativeSurfaceSubregionParity", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.5F); + } + + [WebGPUTheory] + [WithBlankImage(220, 160, PixelTypes.Rgba32)] + public void Process_WithWebGPUBackend_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new(); + IPath blurPath = CreateBlurEllipsePath(); + IPath pixelatePath = CreatePixelateTrianglePath(); + void DrawAction(DrawingCanvas canvas) + { + DrawProcessScenario(canvas); + canvas.Process(blurPath, ctx => ctx.GaussianBlur(6F)); + canvas.Process(pixelatePath, ctx => ctx.Pixelate(10)); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + (Action>)DrawAction); + + DebugSaveBackendPair(provider, "Process", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + + // Differences are visually allowable so use a higher threshold here. + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.0516F); + } + + [WebGPUTheory] + [WithBasicTestPatternImages(420, 220, PixelTypes.Rgba32)] + public void DrawText_WithRepeatedGlyphs_UsesCoverageCache(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 48); + RichTextOptions textOptions = new(font) + { + Origin = new PointF(8, 8), + WrappingLength = 400 + }; + + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + string text = new('A', 200); + Brush brush = Brushes.Solid(Color.Black); + void DrawAction(DrawingCanvas canvas) => canvas.DrawText(textOptions, text, brush, null); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "RepeatedGlyphs", defaultImage, nativeSurfaceImage); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 2F); + + Assert.InRange(nativeSurfaceBackend.TestingPrepareCoverageCallCount, 1, 20); + Assert.True(nativeSurfaceBackend.TestingCompositeCoverageCallCount >= nativeSurfaceBackend.TestingPrepareCoverageCallCount); + Assert.Equal(nativeSurfaceBackend.TestingPrepareCoverageCallCount, nativeSurfaceBackend.TestingReleaseCoverageCallCount); + Assert.Equal(0, nativeSurfaceBackend.TestingLiveCoverageCount); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + + AssertGpuPathWhenRequired(nativeSurfaceBackend); + } + + [WebGPUTheory] + [WithBlankImage(1200, 280, PixelTypes.Rgba32)] + public void DrawText_WithRepeatedGlyphs_AfterClear_UsesBlendFastPath(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 48); + RichTextOptions textOptions = new(font) + { + Origin = new PointF(8, 8), + WrappingLength = 400 + }; + + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + DrawingOptions clearOptions = new() + { + GraphicsOptions = new GraphicsOptions + { + Antialias = false, + AlphaCompositionMode = PixelAlphaCompositionMode.Src, + ColorBlendingMode = PixelColorBlendingMode.Normal, + BlendPercentage = 1F + } + }; + + const int glyphCount = 200; + string text = new('A', glyphCount); + Brush drawBrush = Brushes.Solid(Color.HotPink); + Brush clearBrush = Brushes.Solid(Color.White); + using Image defaultImage = provider.GetImage(); + using (DrawingCanvas defaultClearCanvas = new(Configuration.Default, GetFrameRegion(defaultImage), clearOptions)) + { + defaultClearCanvas.Fill(clearBrush); + defaultClearCanvas.Flush(); + } + + using (DrawingCanvas defaultDrawCanvas = new(Configuration.Default, GetFrameRegion(defaultImage), drawingOptions)) + { + defaultDrawCanvas.DrawText(textOptions, text, drawBrush, null); + defaultDrawCanvas.Flush(); + } + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + Assert.True( + WebGPUTestNativeSurfaceAllocator.TryCreate( + defaultImage.Width, + defaultImage.Height, + out NativeSurface nativeSurface, + out nint textureHandle, + out nint textureViewHandle, + out string createError), + createError); + + try + { + Configuration nativeSurfaceConfiguration = Configuration.Default.Clone(); + nativeSurfaceConfiguration.SetDrawingBackend(nativeSurfaceBackend); + Rectangle targetBounds = defaultImage.Bounds; + + using (DrawingCanvas nativeSurfaceClearCanvas = + new(nativeSurfaceConfiguration, new NativeCanvasFrame(targetBounds, nativeSurface), clearOptions)) + { + nativeSurfaceClearCanvas.Fill(clearBrush); + nativeSurfaceClearCanvas.Flush(); + } + + int nativeSurfaceComputeBatchesBeforeDraw = nativeSurfaceBackend.TestingComputePathBatchCount; + using (DrawingCanvas nativeSurfaceDrawCanvas = + new(nativeSurfaceConfiguration, new NativeCanvasFrame(targetBounds, nativeSurface), drawingOptions)) + { + nativeSurfaceDrawCanvas.DrawText(textOptions, text, drawBrush, null); + nativeSurfaceDrawCanvas.Flush(); + } + + int nativeSurfaceComputeBatchesFromDraw = + nativeSurfaceBackend.TestingComputePathBatchCount - nativeSurfaceComputeBatchesBeforeDraw; + + Assert.True( + WebGPUTestNativeSurfaceAllocator.TryReadTexture( + textureHandle, + defaultImage.Width, + defaultImage.Height, + out Image nativeSurfaceImage, + out string readError), + readError); + + using (nativeSurfaceImage) + { + DebugSaveBackendPair(provider, "RepeatedGlyphs_AfterClear", defaultImage, nativeSurfaceImage); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 2F); + } + + AssertGpuPathWhenRequired(nativeSurfaceBackend); + + if (nativeSurfaceBackend.TestingIsGPUReady) + { + Assert.True( + nativeSurfaceComputeBatchesFromDraw > 0, + "Expected repeated-glyph draw batch to execute via tiled compute composition on the NativeSurface pipeline."); + } + } + finally + { + WebGPUTestNativeSurfaceAllocator.Release(textureHandle, textureViewHandle); + } + } + + private static void RenderWithDefaultBackend(Image image, DrawingOptions options, Action> drawAction) + where TPixel : unmanaged, IPixel + { + using DrawingCanvas canvas = new(Configuration.Default, GetFrameRegion(image), options); + drawAction(canvas); + canvas.Flush(); + } + + private static EllipsePolygon CreateBlurEllipsePath() + => new(new PointF(55, 40), new SizeF(110, 80)); + + private static void DrawProcessScenario(DrawingCanvas canvas) + where TPixel : unmanaged, IPixel + { + canvas.Clear(Brushes.Solid(Color.White)); + + canvas.Draw(Pens.Solid(Color.DimGray, 3), new Rectangle(10, 10, 220, 140)); + canvas.DrawEllipse(Pens.Solid(Color.CornflowerBlue, 6), new PointF(120, 80), new SizeF(110, 70)); + canvas.DrawArc( + Pens.Solid(Color.ForestGreen, 4), + new PointF(120, 80), + new SizeF(90, 46), + rotation: 15, + startAngle: -25, + sweepAngle: 220); + canvas.DrawLine( + Pens.Solid(Color.OrangeRed, 5), + new PointF(18, 140), + new PointF(76, 28), + new PointF(166, 126), + new PointF(222, 20)); + canvas.DrawBezier( + Pens.Solid(Color.MediumVioletRed, 4), + new PointF(20, 80), + new PointF(70, 18), + new PointF(168, 144), + new PointF(220, 78)); + } + + private static IPath CreatePixelateTrianglePath() + { + PathBuilder pathBuilder = new(); + pathBuilder.AddLine(110, 80, 220, 80); + pathBuilder.AddLine(220, 80, 165, 160); + pathBuilder.AddLine(165, 160, 110, 80); + pathBuilder.CloseAllFigures(); + return pathBuilder.Build(); + } + + private static Image RenderWithNativeSurfaceWebGpuBackend( + int width, + int height, + WebGPUDrawingBackend backend, + DrawingOptions options, + Action> drawAction, + Image initialImage = null) + where TPixel : unmanaged, IPixel + { + Assert.True( + WebGPUTestNativeSurfaceAllocator.TryCreate( + width, + height, + out NativeSurface nativeSurface, + out nint textureHandle, + out nint textureViewHandle, + out string createError), + createError); + + try + { + Configuration configuration = Configuration.Default.Clone(); + configuration.SetDrawingBackend(backend); + Rectangle targetBounds = new(0, 0, width, height); + + using DrawingCanvas canvas = + new(configuration, new NativeCanvasFrame(targetBounds, nativeSurface), options); + if (initialImage is not null) + { + Assert.True( + WebGPUTestNativeSurfaceAllocator.TryWriteTexture( + textureHandle, + width, + height, + initialImage, + out string uploadError), + uploadError); + } + + drawAction(canvas); + canvas.Flush(); + + Assert.True( + WebGPUTestNativeSurfaceAllocator.TryReadTexture( + textureHandle, + width, + height, + out Image image, + out string readError), + readError); + + return image; + } + finally + { + WebGPUTestNativeSurfaceAllocator.Release(textureHandle, textureViewHandle); + } + } + + private static void DebugSaveBackendPair( + TestImageProvider provider, + string testName, + Image defaultImage, + Image nativeSurfaceImage, + float tolerantPercentage = 0.0003F) + where TPixel : unmanaged, IPixel + { + defaultImage.DebugSave( + provider, + $"{testName}_Default", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + nativeSurfaceImage.DebugSave( + provider, + $"{testName}_WebGPU_NativeSurface", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + ImageComparer tolerantComparer = ImageComparer.TolerantPercentage(tolerantPercentage); + defaultImage.CompareToReferenceOutput( + tolerantComparer, + provider, + $"{testName}_Default", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + nativeSurfaceImage.CompareToReferenceOutput( + tolerantComparer, + provider, + $"{testName}_WebGPU_NativeSurface", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + private static void DebugSaveBackendPairNoRef( + TestImageProvider provider, + string testName, + Image defaultImage, + Image nativeSurfaceImage) + where TPixel : unmanaged, IPixel + { + defaultImage.DebugSave( + provider, + $"{testName}_Default", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + nativeSurfaceImage.DebugSave( + provider, + $"{testName}_WebGPU_NativeSurface", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + private static void AssertBackendPairSimilarity( + Image defaultImage, + Image nativeSurfaceImage, + float defaultTolerancePercent) + where TPixel : unmanaged, IPixel + { + ImageComparer tolerantComparer = ImageComparer.TolerantPercentage(defaultTolerancePercent); + tolerantComparer.VerifySimilarity(defaultImage, nativeSurfaceImage); + } + + private static void AssertBackendPairSimilarityInRegion( + Image defaultImage, + Image nativeSurfaceImage, + Rectangle region, + float defaultTolerancePercent) + where TPixel : unmanaged, IPixel + { + using Image defaultRegion = defaultImage.Clone(ctx => ctx.Crop(region)); + using Image nativeRegion = nativeSurfaceImage.Clone(ctx => ctx.Crop(region)); + AssertBackendPairSimilarity(defaultRegion, nativeRegion, defaultTolerancePercent); + } + + private static void AssertCoverageExecutionAccounting(WebGPUDrawingBackend backend) + { + Assert.Equal( + backend.TestingPrepareCoverageCallCount, + backend.TestingGPUPrepareCoverageCallCount + backend.TestingFallbackPrepareCoverageCallCount); + Assert.Equal( + backend.TestingCompositeCoverageCallCount, + backend.TestingGPUCompositeCoverageCallCount + backend.TestingFallbackCompositeCoverageCallCount); + } + + private static void AssertGpuPathWhenRequired(WebGPUDrawingBackend backend) + { + bool requireGpuPath = string.Equals( + Environment.GetEnvironmentVariable("IMAGESHARP_REQUIRE_WEBGPU"), + "1", + StringComparison.Ordinal); + + if (!requireGpuPath) + { + return; + } + + Assert.True( + backend.TestingIsGPUReady, + $"WebGPU initialization did not succeed. Reason='{backend.TestingLastGPUInitializationFailure}'. Prepare(total/gpu/fallback)={backend.TestingPrepareCoverageCallCount}/{backend.TestingGPUPrepareCoverageCallCount}/{backend.TestingFallbackPrepareCoverageCallCount}, Composite(total/gpu/fallback)={backend.TestingCompositeCoverageCallCount}/{backend.TestingGPUCompositeCoverageCallCount}/{backend.TestingFallbackCompositeCoverageCallCount}"); + Assert.True( + backend.TestingGPUPrepareCoverageCallCount > 0, + $"No GPU coverage preparation calls were observed. Prepare(total/gpu/fallback)={backend.TestingPrepareCoverageCallCount}/{backend.TestingGPUPrepareCoverageCallCount}/{backend.TestingFallbackPrepareCoverageCallCount}"); + Assert.True( + backend.TestingGPUCompositeCoverageCallCount > 0, + $"No GPU composite calls were observed. Composite(total/gpu/fallback)={backend.TestingCompositeCoverageCallCount}/{backend.TestingGPUCompositeCoverageCallCount}/{backend.TestingFallbackCompositeCoverageCallCount}"); + Assert.Equal( + 0, + backend.TestingFallbackPrepareCoverageCallCount); + Assert.Equal( + 0, + backend.TestingFallbackCompositeCoverageCallCount); + } + + [WebGPUTheory] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32)] + public void DrawPath_Stroke_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + PathBuilder pb = new(); + pb.AddLine(new PointF(30, 50), new PointF(370, 250)); + pb.AddLine(new PointF(370, 250), new PointF(200, 20)); + pb.CloseFigure(); + IPath path = pb.Build(); + Pen pen = Pens.Solid(Color.DarkBlue, 4F); + void DrawAction(DrawingCanvas canvas) => canvas.Draw(pen, path); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "DrawPath_Stroke", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + public static TheoryData LineJoinValues { get; } = new() + { + LineJoin.Miter, + LineJoin.MiterRevert, + LineJoin.MiterRound, + LineJoin.Bevel, + LineJoin.Round + }; + + [WebGPUTheory] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32, LineJoin.Miter)] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32, LineJoin.MiterRevert)] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32, LineJoin.MiterRound)] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32, LineJoin.Bevel)] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32, LineJoin.Round)] + public void DrawPath_Stroke_LineJoin_MatchesDefaultOutput(TestImageProvider provider, LineJoin lineJoin) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + // Sharp angles to exercise join behavior. + PathBuilder pb = new(); + pb.AddLine(new PointF(30, 250), new PointF(100, 30)); + pb.AddLine(new PointF(100, 30), new PointF(170, 250)); + pb.AddLine(new PointF(170, 250), new PointF(240, 30)); + pb.AddLine(new PointF(240, 30), new PointF(370, 150)); + IPath path = pb.Build(); + + Pen pen = new SolidPen(new PenOptions(Color.DarkBlue, 12F) + { + StrokeOptions = new StrokeOptions { LineJoin = lineJoin } + }); + + void DrawAction(DrawingCanvas canvas) => canvas.Draw(pen, path); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, $"DrawPath_Stroke_LineJoin_{lineJoin}", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.01F); + } + + [WebGPUTheory] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32, LineCap.Butt)] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32, LineCap.Square)] + [WithSolidFilledImages(400, 300, "White", PixelTypes.Rgba32, LineCap.Round)] + public void DrawPath_Stroke_LineCap_MatchesDefaultOutput(TestImageProvider provider, LineCap lineCap) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + // Open path to exercise cap behavior at endpoints. + PathBuilder pb = new(); + pb.AddLine(new PointF(50, 150), new PointF(200, 50)); + pb.AddLine(new PointF(200, 50), new PointF(350, 150)); + IPath path = pb.Build(); + + Pen pen = new SolidPen(new PenOptions(Color.DarkBlue, 16F) + { + StrokeOptions = new StrokeOptions { LineCap = lineCap } + }); + + void DrawAction(DrawingCanvas canvas) => canvas.Draw(pen, path); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, $"DrawPath_Stroke_LineCap_{lineCap}", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.01F); + } + + [WebGPUTheory] + [WithSolidFilledImages(512, 512, "White", PixelTypes.Rgba32)] + public void FillPath_MultipleSeparatePaths_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + Brush brush = Brushes.Solid(Color.Black); + void DrawAction(DrawingCanvas canvas) + { + for (int i = 0; i < 20; i++) + { + float x = 20 + (i * 24); + float y = 20 + (i * 22); + canvas.Fill(brush, new RectangularPolygon(x, y, 80, 60)); + } + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_MultipleSeparate", defaultImage, nativeSurfaceImage); + + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_EvenOddRule_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true }, + ShapeOptions = new ShapeOptions + { + IntersectionRule = IntersectionRule.EvenOdd + } + }; + + PathBuilder pathBuilder = new(); + pathBuilder.StartFigure(); + pathBuilder.AddLines( + [ + new PointF(16, 16), + new PointF(240, 16), + new PointF(240, 240), + new PointF(16, 240) + ]); + pathBuilder.CloseFigure(); + + // Inner contour with same winding — EvenOdd should create a hole. + pathBuilder.StartFigure(); + pathBuilder.AddLines( + [ + new PointF(80, 80), + new PointF(176, 80), + new PointF(176, 176), + new PointF(80, 176) + ]); + pathBuilder.CloseFigure(); + + IPath path = pathBuilder.Build(); + Brush brush = Brushes.Solid(Color.Black); + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, path); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_EvenOdd", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + + // EvenOdd with same winding inner contour should create a hole at center. + Assert.Equal(defaultImage[128, 128], nativeSurfaceImage[128, 128]); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.5F); + } + + [WebGPUTheory] + [WithSolidFilledImages(800, 600, "White", PixelTypes.Rgba32)] + public void FillPath_LargeTileCount_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + // Large polygon spanning most of the image to exercise many tiles. + Brush brush = Brushes.Solid(Color.Black); + EllipsePolygon ellipse = new(new PointF(400, 300), new SizeF(700, 500)); + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, ellipse); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_LargeTileCount", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(300, 200, "White", PixelTypes.Rgba32)] + public void MultipleFlushes_OnSameBackend_ProduceCorrectResults(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + Brush redBrush = Brushes.Solid(Color.Red); + Brush blueBrush = Brushes.Solid(Color.Blue); + RectangularPolygon rect1 = new(20, 20, 120, 80); + RectangularPolygon rect2 = new(160, 100, 120, 80); + + // Default backend: two separate flushes + using Image defaultImage = provider.GetImage(); + using (DrawingCanvas canvas1 = new(Configuration.Default, GetFrameRegion(defaultImage), drawingOptions)) + { + canvas1.Fill(redBrush, rect1); + canvas1.Flush(); + } + + using (DrawingCanvas canvas2 = new(Configuration.Default, GetFrameRegion(defaultImage), drawingOptions)) + { + canvas2.Fill(blueBrush, rect2); + canvas2.Flush(); + } + + // Native surface: two separate flushes reusing same backend + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + Assert.True( + WebGPUTestNativeSurfaceAllocator.TryCreate( + defaultImage.Width, + defaultImage.Height, + out NativeSurface nativeSurface, + out nint textureHandle, + out nint textureViewHandle, + out string createError), + createError); + + try + { + Configuration nativeConfig = Configuration.Default.Clone(); + nativeConfig.SetDrawingBackend(nativeSurfaceBackend); + Rectangle targetBounds = defaultImage.Bounds; + + // Upload initial white content + using Image initialImage = provider.GetImage(); + Assert.True( + WebGPUTestNativeSurfaceAllocator.TryWriteTexture( + textureHandle, + defaultImage.Width, + defaultImage.Height, + initialImage, + out string uploadError), + uploadError); + + using (DrawingCanvas canvas1 = + new(nativeConfig, new NativeCanvasFrame(targetBounds, nativeSurface), drawingOptions)) + { + canvas1.Fill(redBrush, rect1); + canvas1.Flush(); + } + + using (DrawingCanvas canvas2 = + new(nativeConfig, new NativeCanvasFrame(targetBounds, nativeSurface), drawingOptions)) + { + canvas2.Fill(blueBrush, rect2); + canvas2.Flush(); + } + + Assert.True( + WebGPUTestNativeSurfaceAllocator.TryReadTexture( + textureHandle, + defaultImage.Width, + defaultImage.Height, + out Image nativeSurfaceImage, + out string readError), + readError); + + using (nativeSurfaceImage) + { + DebugSaveBackendPair(provider, "MultipleFlushes", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + } + finally + { + WebGPUTestNativeSurfaceAllocator.Release(textureHandle, textureViewHandle); + } + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithLinearGradientBrush_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + EllipsePolygon ellipse = new(128, 128, 100); + Brush brush = new LinearGradientBrush( + new PointF(28, 28), + new PointF(228, 228), + GradientRepetitionMode.None, + new ColorStop(0, Color.Red), + new ColorStop(0.5F, Color.Green), + new ColorStop(1, Color.Blue)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, ellipse); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + // MacOS on CI has some outliers with this test, so using a slightly higher tolerance here to avoid noise. + DebugSaveBackendPair(provider, "FillPath_LinearGradient", defaultImage, nativeSurfaceImage, tolerantPercentage: 0.0007F); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithLinearGradientBrush_Repeat_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(16, 16, 224, 224); + Brush brush = new LinearGradientBrush( + new PointF(64, 64), + new PointF(128, 128), + GradientRepetitionMode.Repeat, + new ColorStop(0, Color.Yellow), + new ColorStop(1, Color.Purple)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_LinearGradient_Repeat", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithRadialGradientBrush_SingleCircle_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(16, 16, 224, 224); + Brush brush = new RadialGradientBrush( + new PointF(128, 128), + 100F, + GradientRepetitionMode.None, + new ColorStop(0, Color.White), + new ColorStop(1, Color.DarkRed)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_RadialGradient_Single", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithRadialGradientBrush_TwoCircle_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(16, 16, 224, 224); + Brush brush = new RadialGradientBrush( + new PointF(100, 100), + 20F, + new PointF(128, 128), + 110F, + GradientRepetitionMode.None, + new ColorStop(0, Color.Yellow), + new ColorStop(1, Color.Navy)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_RadialGradient_TwoCircle", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithEllipticGradientBrush_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(16, 16, 224, 224); + Brush brush = new EllipticGradientBrush( + new PointF(128, 128), + new PointF(228, 128), + 0.6F, + GradientRepetitionMode.None, + new ColorStop(0, Color.Cyan), + new ColorStop(1, Color.Magenta)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_EllipticGradient", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithSweepGradientBrush_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + EllipsePolygon ellipse = new(128, 128, 100); + Brush brush = new SweepGradientBrush( + new PointF(128, 128), + 0F, + 360F, + GradientRepetitionMode.None, + new ColorStop(0, Color.Red), + new ColorStop(0.33F, Color.Green), + new ColorStop(0.67F, Color.Blue), + new ColorStop(1, Color.Red)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, ellipse); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_SweepGradient", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithSweepGradientBrush_PartialArc_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(16, 16, 224, 224); + Brush brush = new SweepGradientBrush( + new PointF(128, 128), + 45F, + 270F, + GradientRepetitionMode.Reflect, + new ColorStop(0, Color.Orange), + new ColorStop(1, Color.Teal)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + // MacOS on CI has some outliers with this test, so using a slightly higher tolerance here to avoid noise. + DebugSaveBackendPair( + provider, + "FillPath_SweepGradient_PartialArc", + defaultImage, + nativeSurfaceImage, + tolerantPercentage: 0.0280F); + + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithPatternBrush_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(16, 16, 224, 224); + Brush brush = Brushes.Horizontal(Color.Black, Color.White); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_PatternBrush_Horizontal", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithPatternBrush_Diagonal_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + EllipsePolygon ellipse = new(128, 128, 100); + Brush brush = Brushes.ForwardDiagonal(Color.DarkGreen, Color.LightGray); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, ellipse); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_PatternBrush_Diagonal", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "Red", PixelTypes.Rgba32)] + public void FillPath_WithRecolorBrush_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(16, 16, 224, 224); + Brush brush = new RecolorBrush(Color.Red, Color.Blue, 0.5F); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_RecolorBrush", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithLinearGradientBrush_ThreePoint_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(16, 16, 224, 224); + Brush brush = new LinearGradientBrush( + new PointF(64, 128), + new PointF(192, 128), + new PointF(128, 64), + GradientRepetitionMode.None, + new ColorStop(0, Color.Coral), + new ColorStop(1, Color.SteelBlue)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_LinearGradient_ThreePoint", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(256, 256, "White", PixelTypes.Rgba32)] + public void FillPath_WithEllipticGradientBrush_Reflect_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + RectangularPolygon rect = new(8, 8, 240, 240); + Brush brush = new EllipticGradientBrush( + new PointF(128, 128), + new PointF(180, 160), + 0.4F, + GradientRepetitionMode.Reflect, + new ColorStop(0, Color.Gold), + new ColorStop(0.5F, Color.DarkViolet), + new ColorStop(1, Color.White)); + + void DrawAction(DrawingCanvas canvas) => canvas.Fill(brush, rect); + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "FillPath_EllipticGradient_Reflect", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(500, 400, "Black", PixelTypes.Rgba32)] + public void CanApplyPerspectiveTransform_StarWarsCrawl(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 32); + + const string text = @"A long time ago in a galaxy +far, far away.... + +It is a period of civil war. +Rebel spaceships, striking +from a hidden base, have won +their first victory against +the evil Galactic Empire."; + + RichTextOptions textOptions = new(font) + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Bottom, + TextAlignment = TextAlignment.Center, + Origin = new PointF(250, 360) + }; + + const float originX = 250; + const float originY = 380; + Matrix4x4 toOrigin = Matrix4x4.CreateTranslation(-originX, -originY, 0); + Matrix4x4 taperMatrix = Matrix4x4.Identity; + taperMatrix.M24 = -0.003F; + Matrix4x4 fromOrigin = Matrix4x4.CreateTranslation(originX, originY, 0); + Matrix4x4 perspective = toOrigin * taperMatrix * fromOrigin; + + DrawingOptions perspectiveOptions = new() { Transform = perspective }; + + // Star Destroyer geometry. + PointF[] sternFace = + [ + new(0, 0), new(300, 0), new(300, 80), new(0, 80), + ]; + + RectangularPolygon sternHighlightRect = new(4, 4, 292, 72); + + EllipsePolygon thrusterLeft = new(50, 40, 42, 42); + EllipsePolygon thrusterCenter = new(150, 40, 48, 48); + EllipsePolygon thrusterRight = new(250, 40, 42, 42); + + ProjectiveTransformBuilder transformBuilder = new(); + + Rectangle sternBounds = new(0, 0, 300, 80); + Matrix4x4 sternTransform = transformBuilder + .AppendQuadDistortion( + topLeft: new PointF(70, 80), + topRight: new PointF(380, 90), + bottomRight: new PointF(400, 135), + bottomLeft: new PointF(50, 140)) + .BuildMatrix(sternBounds); + + PointF[] bottomHull = + [ + new(0, 0), new(300, 0), new(150, 80), + ]; + + EllipsePolygon hullDome = new(117, 80, 96, 96); + + Rectangle hullBounds = new(0, 0, 300, 80); + Matrix4x4 hullTransform = transformBuilder.Clear() + .AppendQuadDistortion( + topLeft: new PointF(50, 140), + topRight: new PointF(400, 135), + bottomRight: new PointF(310, 170), + bottomLeft: new PointF(-40, 170)) + .BuildMatrix(hullBounds); + + PointF[] towerStem = + [ + new(14, 8), new(26, 8), new(26, 20), new(14, 20), + ]; + + PointF[] towerTop = + [ + new(0, 0), new(40, 0), new(40, 10), new(0, 10), + ]; + + Rectangle towerBounds = new(0, 0, 40, 20); + Matrix4x4 towerTransform = transformBuilder.Clear() + .AppendQuadDistortion( + topLeft: new PointF(175, 66), + topRight: new PointF(240, 68), + bottomRight: new PointF(238, 85), + bottomLeft: new PointF(177, 84)) + .BuildMatrix(towerBounds); + + Color sternColorLeft = Color.FromPixel(new Rgba32(70, 75, 85, 255)); + Color sternColorRight = Color.FromPixel(new Rgba32(35, 38, 45, 255)); + Color hullColorLeft = Color.FromPixel(new Rgba32(85, 90, 100, 255)); + Color hullColorRight = Color.FromPixel(new Rgba32(45, 50, 58, 255)); + Color highlightColorLeft = Color.FromPixel(new Rgba32(135, 140, 150, 255)); + Color highlightColorRight = Color.FromPixel(new Rgba32(65, 70, 80, 255)); + Color thrusterInnerGlow = Color.White; + Color thrusterOuterGlow = Color.Blue; + + LinearGradientBrush sternBrush = new( + new PointF(0, 40), + new PointF(300, 40), + GradientRepetitionMode.None, + new ColorStop(0, sternColorLeft), + new ColorStop(1, sternColorRight)); + + LinearGradientBrush hullBrush = new( + new PointF(0, 40), + new PointF(300, 40), + GradientRepetitionMode.None, + new ColorStop(0, hullColorLeft), + new ColorStop(1, hullColorRight)); + + LinearGradientBrush highlightBrush = new( + new PointF(0, 40), + new PointF(300, 40), + GradientRepetitionMode.None, + new ColorStop(0, highlightColorLeft), + new ColorStop(1, highlightColorRight)); + + LinearGradientBrush towerBrush = new( + new PointF(0, 10), + new PointF(40, 10), + GradientRepetitionMode.None, + new ColorStop(0, sternColorLeft), + new ColorStop(1, sternColorRight)); + + LinearGradientBrush towerTopBrush = new( + new PointF(0, 5), + new PointF(40, 5), + GradientRepetitionMode.None, + new ColorStop(0, highlightColorLeft), + new ColorStop(1, highlightColorRight)); + + LinearGradientBrush domeBrush = new( + new PointF(21, 80), + new PointF(213, 80), + GradientRepetitionMode.None, + new ColorStop(0, highlightColorLeft), + new ColorStop(1, highlightColorRight)); + + EllipticGradientBrush thrusterBrushLeft = new( + new PointF(50, 40), + new PointF(50 + 42, 40), + 1f, + GradientRepetitionMode.None, + new ColorStop(0, thrusterInnerGlow), + new ColorStop(.75F, thrusterOuterGlow)); + + EllipticGradientBrush thrusterBrushCenter = new( + new PointF(150, 40), + new PointF(150 + 48, 40), + 1f, + GradientRepetitionMode.None, + new ColorStop(0, thrusterInnerGlow), + new ColorStop(.75F, thrusterOuterGlow)); + + EllipticGradientBrush thrusterBrushRight = new( + new PointF(250, 40), + new PointF(250 + 42, 40), + 1f, + GradientRepetitionMode.None, + new ColorStop(0, thrusterInnerGlow), + new ColorStop(.75F, thrusterOuterGlow)); + + DrawingOptions sternOptions = new() { Transform = sternTransform }; + DrawingOptions hullOptions = new() { Transform = hullTransform }; + DrawingOptions towerOptions = new() { Transform = towerTransform }; + + void DrawAction(DrawingCanvas canvas) + { + // Bottom hull (draw first — behind stern). + canvas.Save(hullOptions); + canvas.Fill(highlightBrush, new Polygon(bottomHull)); + canvas.Restore(); + + // Stern face with thrusters, highlight, and dome. + canvas.Save(sternOptions); + canvas.Fill(domeBrush, hullDome); + canvas.Draw(Pens.Solid(highlightColorRight, 2), hullDome); + canvas.Fill(sternBrush, new Polygon(sternFace)); + canvas.Draw(Pens.Solid(highlightColorLeft, 2), sternHighlightRect); + canvas.Fill(thrusterBrushLeft, thrusterLeft); + canvas.Fill(thrusterBrushCenter, thrusterCenter); + canvas.Fill(thrusterBrushRight, thrusterRight); + canvas.Draw(Pens.Solid(highlightColorLeft, 2), thrusterLeft); + canvas.Draw(Pens.Solid(highlightColorLeft, 2), thrusterCenter); + canvas.Draw(Pens.Solid(highlightColorLeft, 2), thrusterRight); + canvas.Restore(); + + // Bridge tower. + canvas.Save(towerOptions); + canvas.Fill(towerTopBrush, new Polygon(towerTop)); + canvas.Fill(towerBrush, new Polygon(towerStem)); + canvas.Restore(); + + // Text crawl with perspective. + canvas.Save(perspectiveOptions); + canvas.DrawText(textOptions, text, Brushes.Solid(Color.Yellow), pen: null); + canvas.Restore(); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "StarWarsCrawl", defaultImage, nativeSurfaceImage); + AssertCoverageExecutionAccounting(nativeSurfaceBackend); + AssertGpuPathWhenRequired(nativeSurfaceBackend); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 0.005F); + } + + [WebGPUTheory] + [WithSolidFilledImages(128, 128, "White", PixelTypes.Rgba32)] + public void SaveLayer_FullOpacity_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new(); + Brush brush = Brushes.Solid(Color.Red); + RectangularPolygon polygon = new(10, 10, 80, 80); + + void DrawAction(DrawingCanvas canvas) + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.SaveLayer(); + canvas.Fill(brush, polygon); + canvas.Restore(); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "SaveLayer_FullOpacity", defaultImage, nativeSurfaceImage); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(128, 128, "White", PixelTypes.Rgba32)] + public void SaveLayer_HalfOpacity_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new(); + Brush brush = Brushes.Solid(Color.Red); + RectangularPolygon polygon = new(10, 10, 80, 80); + + void DrawAction(DrawingCanvas canvas) + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.SaveLayer(new GraphicsOptions { BlendPercentage = 0.5f }); + canvas.Fill(brush, polygon); + canvas.Restore(); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "SaveLayer_HalfOpacity", defaultImage, nativeSurfaceImage); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(128, 128, "White", PixelTypes.Rgba32)] + public void SaveLayer_NestedLayers_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new(); + + static void DrawAction(DrawingCanvas canvas) + { + canvas.Fill(Brushes.Solid(Color.White)); + + // Outer layer: red fill. + canvas.SaveLayer(); + canvas.Fill(Brushes.Solid(Color.Red), new RectangularPolygon(0, 0, 128, 128)); + + // Inner layer: blue fill over center. + canvas.SaveLayer(); + canvas.Fill(Brushes.Solid(Color.Blue), new RectangularPolygon(32, 32, 64, 64)); + canvas.Restore(); // Composites blue onto red. + + canvas.Restore(); // Composites red+blue onto white. + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "SaveLayer_NestedLayers", defaultImage, nativeSurfaceImage); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(128, 128, "White", PixelTypes.Rgba32)] + public void SaveLayer_WithBlendMode_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new(); + + static void DrawAction(DrawingCanvas canvas) + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.Red), new RectangularPolygon(20, 20, 88, 88)); + + canvas.SaveLayer(new GraphicsOptions + { + ColorBlendingMode = PixelColorBlendingMode.Multiply, + AlphaCompositionMode = PixelAlphaCompositionMode.SrcOver, + BlendPercentage = 1f + }); + + canvas.Fill(Brushes.Solid(Color.Blue), new RectangularPolygon(40, 40, 88, 88)); + canvas.Restore(); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "SaveLayer_WithBlendMode", defaultImage, nativeSurfaceImage); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(128, 128, "White", PixelTypes.Rgba32)] + public void SaveLayer_WithBounds_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new(); + + static void DrawAction(DrawingCanvas canvas) + { + canvas.Fill(Brushes.Solid(Color.White)); + + // Layer restricted to a sub-region; draw within the layer's local bounds. + canvas.SaveLayer(new GraphicsOptions(), new Rectangle(16, 16, 96, 96)); + canvas.Fill(Brushes.Solid(Color.Green), new RectangularPolygon(0, 0, 96, 96)); + canvas.Restore(); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "SaveLayer_WithBounds", defaultImage, nativeSurfaceImage); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + [WebGPUTheory] + [WithSolidFilledImages(128, 128, "White", PixelTypes.Rgba32)] + public void SaveLayer_MixedSaveAndSaveLayer_MatchesDefaultOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DrawingOptions drawingOptions = new(); + + static void DrawAction(DrawingCanvas canvas) + { + canvas.Fill(Brushes.Solid(Color.White)); + + int before = canvas.SaveCount; + canvas.Save(); // plain save + canvas.SaveLayer(); // layer + canvas.Save(); // plain save + + canvas.Fill(Brushes.Solid(Color.Green), new RectangularPolygon(0, 0, 128, 128)); + + canvas.RestoreTo(before); + } + + using Image defaultImage = provider.GetImage(); + RenderWithDefaultBackend(defaultImage, drawingOptions, DrawAction); + + using WebGPUDrawingBackend nativeSurfaceBackend = new(); + using Image nativeSurfaceInitialImage = provider.GetImage(); + using Image nativeSurfaceImage = RenderWithNativeSurfaceWebGpuBackend( + defaultImage.Width, + defaultImage.Height, + nativeSurfaceBackend, + drawingOptions, + DrawAction, + nativeSurfaceInitialImage); + + DebugSaveBackendPair(provider, "SaveLayer_MixedSaveAndSaveLayer", defaultImage, nativeSurfaceImage); + AssertBackendPairSimilarity(defaultImage, nativeSurfaceImage, 1F); + } + + private static Buffer2DRegion GetFrameRegion(Image image) + where TPixel : unmanaged, IPixel + => new(image.Frames.RootFrame.PixelBuffer, image.Bounds); +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/Backends/WebGPUTextureFormatMapperTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/Backends/WebGPUTextureFormatMapperTests.cs new file mode 100644 index 000000000..03005556a --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/Backends/WebGPUTextureFormatMapperTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Silk.NET.WebGPU; +using SixLabors.ImageSharp.Drawing.Processing.Backends; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing.Backends; + +public class WebGPUTextureFormatMapperTests +{ + [Fact] + public void Mapper_UsesExactSilkEnumValues_ForAllSupportedFormats() + { + (WebGPUTextureFormatId Drawing, TextureFormat Silk)[] mappings = + [ + (WebGPUTextureFormatId.Rgba8Unorm, TextureFormat.Rgba8Unorm), + (WebGPUTextureFormatId.Rgba8Snorm, TextureFormat.Rgba8Snorm), + (WebGPUTextureFormatId.Rgba8Uint, TextureFormat.Rgba8Uint), + (WebGPUTextureFormatId.Bgra8Unorm, TextureFormat.Bgra8Unorm), + (WebGPUTextureFormatId.Rgba16Uint, TextureFormat.Rgba16Uint), + (WebGPUTextureFormatId.Rgba16Sint, TextureFormat.Rgba16Sint), + (WebGPUTextureFormatId.Rgba16Float, TextureFormat.Rgba16float), + (WebGPUTextureFormatId.Rgba32Float, TextureFormat.Rgba32float) + ]; + + Assert.Equal(Enum.GetValues().Length, mappings.Length); + + foreach ((WebGPUTextureFormatId drawing, TextureFormat silk) in mappings) + { + Assert.Equal((int)silk, (int)drawing); + Assert.Equal(silk, WebGPUTextureFormatMapper.ToSilk(drawing)); + Assert.Equal(drawing, WebGPUTextureFormatMapper.FromSilk(silk)); + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasBatcherTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasBatcherTests.cs new file mode 100644 index 000000000..21ef1d93b --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasBatcherTests.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Processing.Backends; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public class DrawingCanvasBatcherTests +{ + [Fact] + public void Flush_SamePathDifferentBrushes_UsesSingleCoverageDefinition() + { + Configuration configuration = new(); + CapturingBackend backend = new(); + configuration.SetDrawingBackend(backend); + using Image image = new(40, 40); + Buffer2DRegion region = new(image.Frames.RootFrame.PixelBuffer, image.Bounds); + + IPath path = new RectangularPolygon(4, 6, 18, 12); + DrawingOptions options = new(); + using DrawingCanvas canvas = new(configuration, region, options); + Brush brushA = Brushes.Solid(Color.Red); + Brush brushB = Brushes.Solid(Color.Blue); + + canvas.Fill(brushA, path); + canvas.Fill(brushB, path); + canvas.Flush(); + + Assert.True(backend.HasBatch); + Assert.NotNull(backend.LastBatch.Definition.Path); + Assert.Equal(2, backend.LastBatch.Commands.Count); + Assert.Same(brushA, backend.LastBatch.Commands[0].Brush); + Assert.Same(brushB, backend.LastBatch.Commands[1].Brush); + } + + [Fact] + public void Flush_SamePathDifferentBrushes_Stroke_UsesSingleBatch() + { + Configuration configuration = new(); + CapturingBackend backend = new(); + configuration.SetDrawingBackend(backend); + using Image image = new(40, 40); + Buffer2DRegion region = new(image.Frames.RootFrame.PixelBuffer, image.Bounds); + + IPath path = new RectangularPolygon(4, 6, 18, 12); + DrawingOptions options = new(); + using DrawingCanvas canvas = new(configuration, region, options); + Pen penA = Pens.Solid(Color.Red, 2F); + Pen penB = Pens.Solid(Color.Blue, 2F); + + canvas.Draw(penA, path); + canvas.Draw(penB, path); + canvas.Flush(); + + Assert.Single(backend.Batches); + Assert.True(backend.LastBatch.Definition.IsStroke); + Assert.Equal(2, backend.LastBatch.Commands.Count); + } + + [Fact] + public void Flush_SamePathReusedMultipleTimes_BatchesCommands() + { + Configuration configuration = new(); + CapturingBackend backend = new(); + configuration.SetDrawingBackend(backend); + using Image image = new(100, 100); + Buffer2DRegion region = new(image.Frames.RootFrame.PixelBuffer, image.Bounds); + + // Use the same path reference 10 times with different brushes. + IPath path = new RectangularPolygon(10, 10, 40, 40); + DrawingOptions options = new(); + using DrawingCanvas canvas = new(configuration, region, options); + + for (int i = 0; i < 10; i++) + { + canvas.Fill(Brushes.Solid(Color.FromPixel(new Rgba32((byte)i, 0, 0, 255))), path); + } + + canvas.Flush(); + + // All 10 commands share the same path reference → single batch. + Assert.Single(backend.Batches); + Assert.Equal(10, backend.Batches[0].Commands.Count); + } + + [Fact] + public void Flush_RepeatedGlyphs_ReusesCoverageDefinitions() + { + Configuration configuration = new(); + CapturingBackend backend = new(); + configuration.SetDrawingBackend(backend); + using Image image = new(420, 220); + Buffer2DRegion region = new(image.Frames.RootFrame.PixelBuffer, image.Bounds); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 48); + RichTextOptions textOptions = new(font) + { + Origin = new PointF(8, 8), + WrappingLength = 400 + }; + + DrawingOptions drawingOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true } + }; + + string text = new('A', 200); + Brush brush = Brushes.Solid(Color.Black); + + using DrawingCanvas canvas = new(configuration, region, drawingOptions); + canvas.DrawText(textOptions, text, brush, pen: null); + canvas.Flush(); + + int totalCommands = backend.Batches.Sum(b => b.Commands.Count); + Assert.True(totalCommands > 0); + + // The glyph renderer caches paths within 1/8th pixel sub-pixel offset, + // so 200 identical glyphs reuse coverage definitions across sub-pixel variants. + Assert.True( + backend.Batches.Count < 200, + $"Expected coverage reuse but got {backend.Batches.Count} batches for 200 glyphs."); + } + + private sealed class CapturingBackend : IDrawingBackend + { + public List Batches { get; } = []; + + public bool HasBatch { get; private set; } + + public CompositionBatch LastBatch { get; private set; } = new( + new CompositionCoverageDefinition( + 0, + EmptyPath.ClosedPath, + new RasterizerOptions( + Rectangle.Empty, + IntersectionRule.NonZero, + RasterizationMode.Aliased, + RasterizerSamplingOrigin.PixelBoundary, + 0.5f)), + []); + + public void FlushCompositions( + Configuration configuration, + ICanvasFrame target, + CompositionScene compositionScene) + where TPixel : unmanaged, IPixel + { + List batches = CompositionScenePlanner.CreatePreparedBatches( + compositionScene.Commands, + target.Bounds); + if (batches.Count == 0) + { + return; + } + + this.LastBatch = batches[^1]; + this.HasBatch = true; + this.Batches.AddRange(batches); + } + + public bool TryReadRegion( + Configuration configuration, + ICanvasFrame target, + Rectangle sourceRectangle, + Buffer2D destination) + where TPixel : unmanaged, IPixel + => false; + + public void ComposeLayer( + Configuration configuration, + ICanvasFrame source, + ICanvasFrame destination, + Point destinationOffset, + GraphicsOptions options) + where TPixel : unmanaged, IPixel + { + } + + public ICanvasFrame CreateLayerFrame( + Configuration configuration, + ICanvasFrame parentTarget, + int width, + int height) + where TPixel : unmanaged, IPixel + => DefaultDrawingBackend.Instance.CreateLayerFrame(configuration, parentTarget, width, height); + + public void ReleaseFrameResources( + Configuration configuration, + ICanvasFrame target) + where TPixel : unmanaged, IPixel + { + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.BrushAndPenStyles.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.BrushAndPenStyles.cs new file mode 100644 index 000000000..6b8cb7095 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.BrushAndPenStyles.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(320, 200, PixelTypes.Rgba32)] + public void Fill_WithGradientAndPatternBrushes_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Brush linearBrush = new LinearGradientBrush( + new PointF(18, 22), + new PointF(192, 140), + GradientRepetitionMode.None, + new ColorStop(0F, Color.LightYellow), + new ColorStop(0.5F, Color.DeepSkyBlue.WithAlpha(0.85F)), + new ColorStop(1F, Color.MediumBlue.WithAlpha(0.9F))); + + Brush radialBrush = new RadialGradientBrush( + new PointF(238, 88), + 66F, + GradientRepetitionMode.Reflect, + new ColorStop(0F, Color.Orange.WithAlpha(0.95F)), + new ColorStop(1F, Color.MediumVioletRed.WithAlpha(0.25F))); + + Brush hatchBrush = Brushes.ForwardDiagonal(Color.DarkSlateGray.WithAlpha(0.7F), Color.Transparent); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(linearBrush, new Rectangle(14, 14, 176, 126)); + canvas.Fill(radialBrush, new EllipsePolygon(new PointF(236, 90), new SizeF(132, 98))); + canvas.Fill(hatchBrush, CreateClosedPathBuilder()); + canvas.Draw(Pens.DashDot(Color.Black, 3), new Rectangle(10, 10, 300, 180)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(320, 200, PixelTypes.Rgba32)] + public void Draw_WithPatternAndGradientPens_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Brush gradientBrush = new LinearGradientBrush( + new PointF(0, 0), + new PointF(320, 0), + GradientRepetitionMode.Repeat, + new ColorStop(0F, Color.CornflowerBlue), + new ColorStop(0.5F, Color.Gold), + new ColorStop(1F, Color.MediumSeaGreen)); + + Brush patternBrush = Brushes.Vertical(Color.DarkRed.WithAlpha(0.75F), Color.Transparent); + Brush percentBrush = Brushes.Percent20(Color.DarkOrange.WithAlpha(0.85F), Color.Transparent); + + Pen dashPen = Pens.Dash(gradientBrush, 6F); + Pen dotPen = Pens.Dot(patternBrush, 5F); + Pen dashDotPen = Pens.DashDot(percentBrush, 4F); + Pen dashDotDotPen = Pens.DashDotDot(Color.Black.WithAlpha(0.75F), 3F); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Draw(dashPen, new Rectangle(16, 14, 288, 170)); + canvas.DrawEllipse(dotPen, new PointF(162, 100), new SizeF(206, 116)); + canvas.DrawArc(dashDotPen, new PointF(160, 100), new SizeF(148, 84), rotation: 0, startAngle: 20, sweepAngle: 300); + canvas.DrawLine(dashDotDotPen, new PointF(26, 174), new PointF(108, 22), new PointF(212, 164), new PointF(292, 26)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Clear.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Clear.cs new file mode 100644 index 000000000..8ce866531 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Clear.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(256, 160, PixelTypes.Rgba32)] + public void Clear_RegionAndPath_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Fill(Brushes.Solid(Color.MidnightBlue.WithAlpha(0.95F))); + canvas.Fill(Brushes.Solid(Color.Crimson.WithAlpha(0.8F)), new Rectangle(22, 16, 188, 118)); + canvas.DrawEllipse(Pens.Solid(Color.Gold, 5), new PointF(128, 80), new SizeF(140, 90)); + + canvas.Clear(Brushes.Solid(Color.LightYellow.WithAlpha(0.45F)), new Rectangle(56, 36, 108, 64)); + IPath clearPath = new EllipsePolygon(new PointF(178, 80), new SizeF(74, 56)); + canvas.Clear(Brushes.Solid(Color.Transparent), clearPath); + + canvas.Draw(Pens.Solid(Color.Black, 3), new Rectangle(10, 10, 236, 140)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(320, 200, PixelTypes.Rgba32)] + public void Clear_WithClipPath_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.MidnightBlue.WithAlpha(0.95F)), new Rectangle(0, 0, 320, 200)); + canvas.Fill(Brushes.Solid(Color.Crimson.WithAlpha(0.78F)), new Rectangle(26, 18, 268, 164)); + canvas.DrawEllipse(Pens.Solid(Color.Gold, 5F), new PointF(160, 100), new SizeF(196, 116)); + + IPath clipPath = new EllipsePolygon(new PointF(160, 100), new SizeF(214, 126)); + _ = canvas.Save(new DrawingOptions(), clipPath); + + canvas.Clear(Brushes.Solid(Color.LightYellow.WithAlpha(0.85F))); + canvas.Clear(Brushes.Solid(Color.MediumPurple.WithAlpha(0.72F)), new Rectangle(40, 24, 108, 72)); + canvas.Clear(Brushes.Solid(Color.LightSeaGreen.WithAlpha(0.8F)), new Rectangle(172, 96, 110, 70)); + canvas.Clear(Brushes.Solid(Color.Transparent), new EllipsePolygon(new PointF(164, 98), new SizeF(74, 48))); + + canvas.Restore(); + + canvas.Draw(Pens.DashDot(Color.Black, 3F), clipPath); + canvas.Draw(Pens.Solid(Color.Black, 2F), new Rectangle(8, 8, 304, 184)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.DrawImage.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.DrawImage.cs new file mode 100644 index 000000000..52c902fc3 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.DrawImage.cs @@ -0,0 +1,106 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBasicTestPatternImages(384, 256, PixelTypes.Rgba32)] + public void DrawImage_WithRotationTransform_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image foreground = provider.GetImage(); + using Image target = new(384, 256); + + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation(MathF.PI / 4F, new Vector2(192F, 128F))) + }; + + using DrawingCanvas canvas = CreateCanvas(provider, target, options); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.DrawImage( + foreground, + foreground.Bounds, + new RectangleF(72, 48, 240, 160), + KnownResamplers.NearestNeighbor); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(320, 220, PixelTypes.Rgba32)] + public void DrawImage_WithSourceClippingAndScaling_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image foreground = provider.GetImage(); + using Image target = new(320, 220); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.DrawImage( + foreground, + new Rectangle(-48, 18, 196, 148), + new RectangleF(18, 20, 170, 120), + KnownResamplers.Bicubic); + canvas.DrawImage( + foreground, + new Rectangle(220, 100, 160, 140), + new RectangleF(170, 72, 130, 110), + KnownResamplers.NearestNeighbor); + canvas.Draw(Pens.Solid(Color.Black, 3), new Rectangle(8, 8, 304, 204)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(360, 240, PixelTypes.Rgba32)] + public void DrawImage_WithClipPathAndTransform_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image foreground = provider.GetImage(); + using Image target = new(360, 240); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + DrawingOptions transformedOptions = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation(0.32F, new Vector2(180, 120))) + }; + + IPath clipPath = new EllipsePolygon(new PointF(180, 120), new SizeF(208, 126)); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.LightGray.WithAlpha(0.45F)), new Rectangle(18, 16, 324, 208)); + + _ = canvas.Save(transformedOptions, clipPath); + canvas.DrawImage( + foreground, + new Rectangle(10, 8, 234, 180), + new RectangleF(64, 36, 232, 164), + KnownResamplers.Bicubic); + canvas.DrawImage( + foreground, + new Rectangle(102, 32, 196, 166), + new RectangleF(92, 58, 210, 148), + KnownResamplers.NearestNeighbor); + canvas.Restore(); + + canvas.Draw(Pens.DashDot(Color.DarkSlateGray, 3F), clipPath); + canvas.Draw(Pens.Solid(Color.Black, 2F), new Rectangle(8, 8, 344, 224)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Factory.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Factory.cs new file mode 100644 index 000000000..bf557661f --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Factory.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Fact] + public void FromFrame_TargetsProvidedFrame() + { + using Image image = new(48, 36); + + using (DrawingCanvas canvas = DrawingCanvas.FromFrame( + image.Frames.RootFrame, + new DrawingOptions())) + { + canvas.Clear(Brushes.Solid(Color.SeaGreen)); + canvas.Flush(); + } + + Assert.Equal(Color.SeaGreen.ToPixel(), image[12, 10]); + } + + [Fact] + public void FromImage_TargetsRequestedFrame() + { + using Image image = new(40, 30); + image.Frames.AddFrame(image.Frames.RootFrame); + + using (DrawingCanvas rootCanvas = DrawingCanvas.FromRootFrame(image, new DrawingOptions())) + { + rootCanvas.Clear(Brushes.Solid(Color.White)); + rootCanvas.Flush(); + } + + using (DrawingCanvas secondCanvas = DrawingCanvas.FromImage(image, 1, new DrawingOptions())) + { + secondCanvas.Clear(Brushes.Solid(Color.MediumPurple)); + secondCanvas.Flush(); + } + + Assert.Equal(Color.White.ToPixel(), image.Frames.RootFrame[8, 8]); + Assert.Equal(Color.MediumPurple.ToPixel(), image.Frames[1][8, 8]); + } + + [Fact] + public void FromImage_InvalidFrameIndex_Throws() + { + using Image image = new(20, 20); + image.Frames.AddFrame(image.Frames.RootFrame); + + ArgumentOutOfRangeException low = Assert.Throws( + () => DrawingCanvas.FromImage(image, -1, new DrawingOptions())); + ArgumentOutOfRangeException high = Assert.Throws( + () => DrawingCanvas.FromImage(image, image.Frames.Count, new DrawingOptions())); + + Assert.Equal("frameIndex", low.ParamName); + Assert.Equal("frameIndex", high.ParamName); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Guards.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Guards.cs new file mode 100644 index 000000000..2fbbdb861 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Guards.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Fact] + public void RestoreTo_InvalidCount_Throws() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas( + provider, + target, + new DrawingOptions()); + + ArgumentOutOfRangeException low = Assert.Throws(() => canvas.RestoreTo(0)); + ArgumentOutOfRangeException high = Assert.Throws(() => canvas.RestoreTo(2)); + + Assert.Equal("saveCount", low.ParamName); + Assert.Equal("saveCount", high.ParamName); + } + + [Fact] + public void Dispose_ThenOperations_ThrowObjectDisposedException() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(96, 96); + using Image source = new(24, 24); + using DrawingCanvas canvas = CreateCanvas( + provider, + target, + new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 16); + RichTextOptions textOptions = new(font) + { + Origin = new PointF(10, 28) + }; + + canvas.Dispose(); + + Assert.Throws(() => canvas.Fill(Brushes.Solid(Color.Black))); + Assert.Throws(() => canvas.Draw(Pens.Solid(Color.Black, 2F), new Rectangle(8, 8, 60, 60))); + Assert.Throws(() => canvas.DrawText(textOptions, "Disposed", Brushes.Solid(Color.DarkBlue), pen: null)); + Assert.Throws(() => canvas.DrawImage(source, source.Bounds, new RectangleF(12, 12, 48, 48))); + Assert.Throws(canvas.Flush); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathBuilderDraw.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathBuilderDraw.cs new file mode 100644 index 000000000..f369fe93e --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathBuilderDraw.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(192, 128, PixelTypes.Rgba32)] + public void Draw_PathBuilder_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation(-0.15F, new Vector2(96F, 64F))) + }; + + using DrawingCanvas canvas = CreateCanvas(provider, target, options); + PathBuilder pathBuilder = CreateOpenPathBuilder(); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Draw(Pens.Solid(Color.CornflowerBlue, 6F), pathBuilder); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathBuilderFill.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathBuilderFill.cs new file mode 100644 index 000000000..de5d52f87 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathBuilderFill.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(192, 128, PixelTypes.Rgba32)] + public void Fill_PathBuilder_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation(0.2F, new Vector2(96F, 64F))) + }; + + using DrawingCanvas canvas = CreateCanvas(provider, target, options); + PathBuilder pathBuilder = CreateClosedPathBuilder(); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.DeepPink.WithAlpha(0.85F)), pathBuilder); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathRules.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathRules.cs new file mode 100644 index 000000000..95afcbf46 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.PathRules.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(360, 220, PixelTypes.Rgba32)] + public void Fill_SelfIntersectingPath_EvenOddVsNonZero_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + IPath leftPath = CreatePentagramPath(new PointF(96, 110), 78F); + IPath rightPath = CreatePentagramPath(new PointF(264, 110), 78F); + + DrawingOptions evenOddOptions = new() + { + ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.EvenOdd } + }; + + DrawingOptions nonZeroOptions = new() + { + ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.NonZero } + }; + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.AliceBlue.WithAlpha(0.7F)), new Rectangle(12, 12, 336, 196)); + + _ = canvas.Save(evenOddOptions); + canvas.Fill(Brushes.Solid(Color.DeepPink.WithAlpha(0.85F)), leftPath); + canvas.Restore(); + + _ = canvas.Save(nonZeroOptions); + canvas.Fill(Brushes.Solid(Color.DeepPink.WithAlpha(0.85F)), rightPath); + canvas.Restore(); + + canvas.Draw(Pens.Solid(Color.Black, 3F), leftPath); + canvas.Draw(Pens.Solid(Color.Black, 3F), rightPath); + canvas.DrawLine(Pens.Dash(Color.Gray, 2F), new PointF(180, 20), new PointF(180, 200)); + canvas.Draw(Pens.Solid(Color.Black, 2F), new Rectangle(8, 8, 344, 204)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + private static IPath CreatePentagramPath(PointF center, float radius) + { + PointF[] points = new PointF[5]; + for (int i = 0; i < points.Length; i++) + { + float angle = (-MathF.PI / 2F) + (i * (MathF.PI * 2F / points.Length)); + points[i] = new PointF( + center.X + (radius * MathF.Cos(angle)), + center.Y + (radius * MathF.Sin(angle))); + } + + int[] order = [0, 2, 4, 1, 3, 0]; + PathBuilder builder = new(); + for (int i = 0; i < order.Length - 1; i++) + { + PointF a = points[order[i]]; + PointF b = points[order[i + 1]]; + builder.AddLine(a.X, a.Y, b.X, b.Y); + } + + builder.CloseAllFigures(); + return builder.Build(); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Primitives.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Primitives.cs new file mode 100644 index 000000000..16e3e5f04 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Primitives.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(50, 50, PixelTypes.Rgba32)] + public void FillRectangle_AliasedRendersFullCorners(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + const int x = 10; + const int y = 10; + const int w = 30; + const int h = 20; + + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = false } + }; + + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, options); + + canvas.Clear(Brushes.Solid(Color.Black)); + canvas.Fill(Brushes.Solid(Color.White), new Rectangle(x, y, w, h)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + + // Verify all four corner pixels are fully white. + Rgba32 topLeft = target[x, y].ToRgba32(); + Rgba32 topRight = target[x + w - 1, y].ToRgba32(); + Rgba32 bottomLeft = target[x, y + h - 1].ToRgba32(); + Rgba32 bottomRight = target[x + w - 1, y + h - 1].ToRgba32(); + + Assert.Equal(255, topLeft.R); + Assert.Equal(255, topRight.R); + Assert.Equal(255, bottomLeft.R); + Assert.Equal(255, bottomRight.R); + + // Verify pixels just outside each corner are still black. + Assert.Equal(0, target[x - 1, y].ToRgba32().R); + Assert.Equal(0, target[x, y - 1].ToRgba32().R); + Assert.Equal(0, target[x + w, y].ToRgba32().R); + Assert.Equal(0, target[x + w - 1, y - 1].ToRgba32().R); + Assert.Equal(0, target[x - 1, y + h - 1].ToRgba32().R); + Assert.Equal(0, target[x, y + h].ToRgba32().R); + Assert.Equal(0, target[x + w, y + h - 1].ToRgba32().R); + Assert.Equal(0, target[x + w - 1, y + h].ToRgba32().R); + + // Verify interior pixel count matches expected area. + int whiteCount = 0; + target.ProcessPixelRows(accessor => + { + for (int row = 0; row < accessor.Height; row++) + { + Span span = accessor.GetRowSpan(row); + for (int col = 0; col < span.Length; col++) + { + if (span[col].ToRgba32().R == 255) + { + whiteCount++; + } + } + } + }); + + Assert.Equal(w * h, whiteCount); + } + + [Theory] + [WithBlankImage(50, 50, PixelTypes.Rgba32)] + public void DrawRectangle_AliasedRendersFullCorners(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // A 2px pen centered on the rectangle edge places 1px inside and 1px outside. + // For a rect at (10,10) size 30x20, the outer stroke boundary is (9,9)-(40,30) + // and the inner boundary is (11,11)-(38,28). + // Miter join ensures corners are fully filled (bevel would cut them). + const int x = 10; + const int y = 10; + const int w = 30; + const int h = 20; + const float penWidth = 2; + + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = false, AntialiasThreshold = 0.01F } + }; + + SolidPen pen = new(new PenOptions(Color.White, penWidth, null) + { + StrokeOptions = new StrokeOptions { LineJoin = LineJoin.Miter } + }); + + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, options); + + canvas.Clear(Brushes.Solid(Color.Black)); + canvas.Draw(pen, new Rectangle(x, y, w, h)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + + // Outer corners of the stroke (1px outside the rect edge). + Assert.Equal(255, target[x - 1, y - 1].ToRgba32().R); + Assert.Equal(255, target[x + w, y - 1].ToRgba32().R); + Assert.Equal(255, target[x - 1, y + h].ToRgba32().R); + Assert.Equal(255, target[x + w, y + h].ToRgba32().R); + + // Inner corners of the stroke (1px inside the rect edge). + Assert.Equal(255, target[x, y].ToRgba32().R); + Assert.Equal(255, target[x + w - 1, y].ToRgba32().R); + Assert.Equal(255, target[x, y + h - 1].ToRgba32().R); + Assert.Equal(255, target[x + w - 1, y + h - 1].ToRgba32().R); + + // Well outside the stroke boundary should be black. + Assert.Equal(0, target[x - 3, y - 3].ToRgba32().R); + Assert.Equal(0, target[x + w + 2, y - 3].ToRgba32().R); + Assert.Equal(0, target[x - 3, y + h + 2].ToRgba32().R); + Assert.Equal(0, target[x + w + 2, y + h + 2].ToRgba32().R); + + // Interior of the rectangle (well inside the stroke) should be black. + Assert.Equal(0, target[x + (w / 2), y + (h / 2)].ToRgba32().R); + } + + [Theory] + [WithBlankImage(240, 160, PixelTypes.Rgba32)] + public void DrawPrimitiveHelpers_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Clear(Brushes.Solid(Color.White)); + + canvas.Draw(Pens.Solid(Color.DimGray, 3), new Rectangle(10, 10, 220, 140)); + canvas.DrawEllipse(Pens.Solid(Color.CornflowerBlue, 6), new PointF(120, 80), new SizeF(110, 70)); + canvas.DrawArc( + Pens.Solid(Color.ForestGreen, 4), + new PointF(120, 80), + new SizeF(90, 46), + rotation: 15, + startAngle: -25, + sweepAngle: 220); + canvas.DrawLine( + Pens.Solid(Color.OrangeRed, 5), + new PointF(18, 140), + new PointF(76, 28), + new PointF(166, 126), + new PointF(222, 20)); + canvas.DrawBezier( + Pens.Solid(Color.MediumVioletRed, 4), + new PointF(20, 80), + new PointF(70, 18), + new PointF(168, 144), + new PointF(220, 78)); + + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Process.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Process.cs new file mode 100644 index 000000000..9e4455c69 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Process.cs @@ -0,0 +1,256 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Processing.Backends; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(220, 160, PixelTypes.Rgba32)] + public void Process_PathBuilder_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + + PathBuilder blurBuilder = new(); + blurBuilder.AddArc(new PointF(55, 40), 55, 40, 0, 0, 360); + blurBuilder.CloseAllFigures(); + + PathBuilder pixelateBuilder = new(); + pixelateBuilder.AddLine(110, 80, 220, 80); + pixelateBuilder.AddLine(220, 80, 165, 160); + pixelateBuilder.AddLine(165, 160, 110, 80); + pixelateBuilder.CloseAllFigures(); + + using (DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions())) + { + DrawProcessScenario(canvas); + canvas.Process(blurBuilder, ctx => ctx.GaussianBlur(6F)); + canvas.Process(pixelateBuilder, ctx => ctx.Pixelate(10)); + canvas.Flush(); + } + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(220, 160, PixelTypes.Rgba32)] + public void Process_Path_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + IPath blurPath = CreateBlurEllipsePath(); + IPath pixelatePath = CreatePixelateTrianglePath(); + + using (DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions())) + { + DrawProcessScenario(canvas); + canvas.Process(blurPath, ctx => ctx.GaussianBlur(6F)); + canvas.Process(pixelatePath, ctx => ctx.Pixelate(10)); + canvas.Flush(); + } + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(220, 160, PixelTypes.Rgba32)] + public void Process_NoCpuFrame_WithReadbackCapability_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + IPath blurPath = CreateBlurEllipsePath(); + IPath pixelatePath = CreatePixelateTrianglePath(); + + Buffer2DRegion targetRegion = new(target.Frames.RootFrame.PixelBuffer, target.Bounds); + MemoryCanvasFrame proxyFrame = new(targetRegion); + MirroringCpuReadbackTestBackend mirroringBackend = new(proxyFrame, target); + + NativeSurface nativeSurface = new(TPixel.GetPixelTypeInfo()); + Configuration configuration = provider.Configuration.Clone(); + configuration.SetDrawingBackend(mirroringBackend); + + using (DrawingCanvas canvas = new( + configuration, + new NativeCanvasFrame(target.Bounds, nativeSurface), + new DrawingOptions())) + { + DrawProcessScenario(canvas); + canvas.Process(blurPath, ctx => ctx.GaussianBlur(6F)); + canvas.Process(pixelatePath, ctx => ctx.Pixelate(10)); + canvas.Flush(); + } + + Assert.True(mirroringBackend.ReadbackCallCount > 0); + Assert.Same(configuration, mirroringBackend.LastReadbackConfiguration); + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Fact] + public void Process_UsesCanvasConfigurationForOperationContext() + { + Configuration configuration = Configuration.Default.Clone(); + using Image target = new(configuration, 48, 36); + Buffer2DRegion targetRegion = new(target.Frames.RootFrame.PixelBuffer, target.Bounds); + using DrawingCanvas canvas = new(configuration, targetRegion, new DrawingOptions()); + + bool callbackInvoked = false; + bool sameConfiguration = false; + + canvas.Fill(Brushes.Solid(Color.CornflowerBlue)); + canvas.Process(new Rectangle(8, 6, 28, 20), ctx => + { + callbackInvoked = true; + sameConfiguration = ReferenceEquals(configuration, ctx.Configuration); + ctx.GaussianBlur(2F); + }); + canvas.Flush(); + + Assert.True(callbackInvoked); + Assert.True(sameConfiguration); + } + + private static void DrawProcessScenario(IDrawingCanvas canvas) + { + canvas.Clear(Brushes.Solid(Color.White)); + + canvas.Draw(Pens.Solid(Color.DimGray, 3), new Rectangle(10, 10, 220, 140)); + canvas.DrawEllipse(Pens.Solid(Color.CornflowerBlue, 6), new PointF(120, 80), new SizeF(110, 70)); + canvas.DrawArc( + Pens.Solid(Color.ForestGreen, 4), + new PointF(120, 80), + new SizeF(90, 46), + rotation: 15, + startAngle: -25, + sweepAngle: 220); + canvas.DrawLine( + Pens.Solid(Color.OrangeRed, 5), + new PointF(18, 140), + new PointF(76, 28), + new PointF(166, 126), + new PointF(222, 20)); + canvas.DrawBezier( + Pens.Solid(Color.MediumVioletRed, 4), + new PointF(20, 80), + new PointF(70, 18), + new PointF(168, 144), + new PointF(220, 78)); + } + + private static EllipsePolygon CreateBlurEllipsePath() + => new(new PointF(55, 40), new SizeF(110, 80)); + + private static IPath CreatePixelateTrianglePath() + { + PathBuilder pathBuilder = new(); + pathBuilder.AddLine(110, 80, 220, 80); + pathBuilder.AddLine(220, 80, 165, 160); + pathBuilder.AddLine(165, 160, 110, 80); + pathBuilder.CloseAllFigures(); + return pathBuilder.Build(); + } + + /// + /// Test backend that mirrors composition output into a CPU frame and optionally serves readback + /// from a backing image so Process-path tests can exercise both readback and shadow-fallback flows. + /// + private sealed class MirroringCpuReadbackTestBackend : IDrawingBackend + where TPixel : unmanaged, IPixel + { + private readonly ICanvasFrame proxyFrame; + private readonly Image? readbackSource; + + public MirroringCpuReadbackTestBackend(ICanvasFrame proxyFrame, Image? readbackSource = null) + { + this.proxyFrame = proxyFrame; + this.readbackSource = readbackSource; + } + + public int ReadbackCallCount { get; private set; } + + public Configuration? LastReadbackConfiguration { get; private set; } + + public void FlushCompositions( + Configuration configuration, + ICanvasFrame target, + CompositionScene compositionScene) + where TTargetPixel : unmanaged, IPixel + { + if (this.proxyFrame is not ICanvasFrame typedProxyFrame) + { + throw new NotSupportedException("Mirroring test backend pixel format mismatch."); + } + + DefaultDrawingBackend.Instance.FlushCompositions(configuration, typedProxyFrame, compositionScene); + } + + public bool TryReadRegion( + Configuration configuration, + ICanvasFrame target, + Rectangle sourceRectangle, + Buffer2D destination) + where TTargetPixel : unmanaged, IPixel + { + this.LastReadbackConfiguration = configuration; + + if (this.readbackSource is null) + { + return false; + } + + this.ReadbackCallCount++; + + Rectangle clipped = Rectangle.Intersect(this.readbackSource.Bounds, sourceRectangle); + if (clipped.Width <= 0 || clipped.Height <= 0) + { + return false; + } + + using Image cropped = this.readbackSource.Clone(ctx => ctx.Crop(clipped)); + using Image converted = cropped.CloneAs(); + Buffer2D source = converted.Frames.RootFrame.PixelBuffer; + int copyWidth = Math.Min(source.Width, destination.Width); + int copyHeight = Math.Min(source.Height, destination.Height); + for (int y = 0; y < copyHeight; y++) + { + source.DangerousGetRowSpan(y).Slice(0, copyWidth).CopyTo(destination.DangerousGetRowSpan(y)); + } + + return true; + } + + public void ComposeLayer( + Configuration configuration, + ICanvasFrame source, + ICanvasFrame destination, + Point destinationOffset, + GraphicsOptions options) + where TTargetPixel : unmanaged, IPixel + => DefaultDrawingBackend.Instance.ComposeLayer(configuration, source, destination, destinationOffset, options); + + public ICanvasFrame CreateLayerFrame( + Configuration configuration, + ICanvasFrame parentTarget, + int width, + int height) + where TTargetPixel : unmanaged, IPixel + => DefaultDrawingBackend.Instance.CreateLayerFrame(configuration, parentTarget, width, height); + + public void ReleaseFrameResources( + Configuration configuration, + ICanvasFrame target) + where TTargetPixel : unmanaged, IPixel + { + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.RegionAndState.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.RegionAndState.cs new file mode 100644 index 000000000..1cd08602f --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.RegionAndState.cs @@ -0,0 +1,179 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(256, 160, PixelTypes.Rgba32)] + public void CreateRegion_LocalCoordinates_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Clear(Brushes.Solid(Color.White)); + + using (DrawingCanvas regionCanvas = canvas.CreateRegion(new Rectangle(40, 24, 140, 96))) + { + regionCanvas.Fill(Brushes.Solid(Color.LightSeaGreen.WithAlpha(0.8F)), new Rectangle(10, 8, 80, 46)); + regionCanvas.Draw(Pens.Solid(Color.DarkBlue, 5), new Rectangle(0, 0, 140, 96)); + regionCanvas.DrawLine( + Pens.Solid(Color.OrangeRed, 4), + new PointF(0, 95), + new PointF(139, 0)); + } + + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(192, 128, PixelTypes.Rgba32)] + public void SaveRestore_ClipPath_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Clear(Brushes.Solid(Color.White)); + + IPath clipPath = new EllipsePolygon(new PointF(96, 64), new SizeF(120, 76)); + _ = canvas.Save(new DrawingOptions(), clipPath); + + canvas.Fill(Brushes.Solid(Color.MediumVioletRed.WithAlpha(0.85F)), new Rectangle(0, 0, 192, 128)); + canvas.Draw(Pens.Solid(Color.Black, 3), new Rectangle(24, 16, 144, 96)); + + canvas.Restore(); + + canvas.Fill(Brushes.Solid(Color.SteelBlue.WithAlpha(0.75F)), new Rectangle(0, 96, 192, 32)); + canvas.Draw(Pens.Solid(Color.DarkGreen, 4), new Rectangle(8, 8, 176, 112)); + + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + + ImageComparer tolerantComparer = ImageComparer.TolerantPercentage(0.0003F); + target.CompareToReferenceOutput(tolerantComparer, provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(224, 160, PixelTypes.Rgba32)] + public void RestoreTo_MultipleStates_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Clear(Brushes.Solid(Color.White)); + + DrawingOptions firstOptions = new() + { + Transform = Matrix4x4.CreateTranslation(20F, 12F, 0) + }; + + int firstSaveCount = canvas.Save(firstOptions, new RectangularPolygon(20, 20, 144, 104)); + canvas.Fill(Brushes.Solid(Color.SkyBlue.WithAlpha(0.8F)), new Rectangle(0, 0, 120, 84)); + + DrawingOptions secondOptions = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation(0.24F, new Vector2(112, 80))) + }; + + _ = canvas.Save(secondOptions, new EllipsePolygon(new PointF(112, 80), new SizeF(130, 90))); + canvas.Draw(Pens.Solid(Color.MediumPurple, 6), new Rectangle(34, 26, 152, 108)); + + canvas.RestoreTo(firstSaveCount); + canvas.DrawLine( + Pens.Solid(Color.OrangeRed, 5), + new PointF(0, 100), + new PointF(76, 18), + new PointF(168, 92)); + + canvas.RestoreTo(1); + canvas.Fill(Brushes.Solid(Color.Gold.WithAlpha(0.7F)), new Rectangle(156, 106, 48, 34)); + canvas.Draw(Pens.Solid(Color.DarkSlateGray, 4), new Rectangle(8, 8, 208, 144)); + + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(320, 220, PixelTypes.Rgba32)] + public void CreateRegion_NestedRegionsAndStateIsolation_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.GhostWhite.WithAlpha(0.85F)), new Rectangle(12, 12, 296, 196)); + + DrawingOptions rootOptions = new() + { + Transform = Matrix4x4.CreateTranslation(6F, 4F, 0) + }; + + IPath rootClip = new EllipsePolygon(new PointF(160, 110), new SizeF(252, 164)); + _ = canvas.Save(rootOptions, rootClip); + + using (DrawingCanvas outerRegion = canvas.CreateRegion(new Rectangle(30, 24, 240, 156))) + { + outerRegion.Fill(Brushes.Solid(Color.LightBlue.WithAlpha(0.35F)), new Rectangle(0, 0, 240, 156)); + outerRegion.Draw(Pens.Solid(Color.DarkBlue, 3F), new Rectangle(0, 0, 240, 156)); + + DrawingOptions outerOptions = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation(0.18F, new Vector2(120, 78))) + }; + + _ = outerRegion.Save(outerOptions, new RectangularPolygon(18, 14, 204, 128)); + outerRegion.Fill(Brushes.Solid(Color.MediumPurple.WithAlpha(0.35F)), new Rectangle(16, 16, 208, 124)); + + using (DrawingCanvas innerRegion = outerRegion.CreateRegion(new Rectangle(52, 34, 132, 82))) + { + innerRegion.Clear(Brushes.Solid(Color.LightGoldenrodYellow.WithAlpha(0.8F))); + + DrawingOptions innerOptions = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateSkew(0.18F, 0F)) + }; + + _ = innerRegion.Save(innerOptions, new EllipsePolygon(new PointF(66, 41), new SizeF(102, 58))); + innerRegion.Fill(Brushes.Solid(Color.SeaGreen.WithAlpha(0.55F)), new Rectangle(0, 0, 132, 82)); + innerRegion.DrawLine( + Pens.Solid(Color.DarkRed, 4F), + new PointF(0, 80), + new PointF(66, 0), + new PointF(132, 74)); + innerRegion.Restore(); + + innerRegion.Draw(Pens.DashDot(Color.Black.WithAlpha(0.75F), 2F), new Rectangle(4, 4, 124, 74)); + } + + outerRegion.Restore(); + + outerRegion.Fill(Brushes.Solid(Color.OrangeRed.WithAlpha(0.6F)), new Rectangle(8, 112, 90, 30)); + outerRegion.DrawLine(Pens.Solid(Color.Black, 3F), new PointF(8, 8), new PointF(232, 148)); + } + + canvas.RestoreTo(1); + canvas.Draw(Pens.Solid(Color.DarkSlateGray, 3F), new Rectangle(8, 8, 304, 204)); + canvas.DrawLine(Pens.Dash(Color.Gray, 2F), new PointF(20, 200), new PointF(300, 20)); + + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.SaveCount.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.SaveCount.cs new file mode 100644 index 000000000..5ddbfc48a --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.SaveCount.cs @@ -0,0 +1,130 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Fact] + public void SaveCount_InitialValue_IsOne() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Assert.Equal(1, canvas.SaveCount); + } + + [Fact] + public void Save_IncrementsSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Assert.Equal(1, canvas.SaveCount); + + int count1 = canvas.Save(); + Assert.Equal(2, count1); + Assert.Equal(2, canvas.SaveCount); + + int count2 = canvas.Save(); + Assert.Equal(3, count2); + Assert.Equal(3, canvas.SaveCount); + } + + [Fact] + public void SaveWithOptions_IncrementsSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + int count = canvas.Save(new DrawingOptions(), new RectangularPolygon(0, 0, 32, 32)); + Assert.Equal(2, count); + Assert.Equal(2, canvas.SaveCount); + } + + [Fact] + public void Restore_DecrementsSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + _ = canvas.Save(); + _ = canvas.Save(); + Assert.Equal(3, canvas.SaveCount); + + canvas.Restore(); + Assert.Equal(2, canvas.SaveCount); + + canvas.Restore(); + Assert.Equal(1, canvas.SaveCount); + } + + [Fact] + public void Restore_AtRootState_DoesNotDecrementBelowOne() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Assert.Equal(1, canvas.SaveCount); + + canvas.Restore(); + Assert.Equal(1, canvas.SaveCount); + + canvas.Restore(); + Assert.Equal(1, canvas.SaveCount); + } + + [Fact] + public void RestoreTo_SetsSaveCountToSpecifiedLevel() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + _ = canvas.Save(); + int mid = canvas.Save(); + _ = canvas.Save(); + _ = canvas.Save(); + Assert.Equal(5, canvas.SaveCount); + + canvas.RestoreTo(mid); + Assert.Equal(mid, canvas.SaveCount); + } + + [Fact] + public void RestoreTo_One_RestoresToRoot() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + _ = canvas.Save(); + _ = canvas.Save(); + _ = canvas.Save(); + + canvas.RestoreTo(1); + Assert.Equal(1, canvas.SaveCount); + } + + [Fact] + public void Save_ReturnValue_MatchesSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + for (int i = 0; i < 5; i++) + { + int returned = canvas.Save(); + Assert.Equal(canvas.SaveCount, returned); + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.SaveLayer.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.SaveLayer.cs new file mode 100644 index 000000000..c4d0176ad --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.SaveLayer.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Fact] + public void SaveLayer_IncrementsSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Assert.Equal(1, canvas.SaveCount); + + int count = canvas.SaveLayer(); + Assert.Equal(2, count); + Assert.Equal(2, canvas.SaveCount); + } + + [Fact] + public void SaveLayer_WithOptions_IncrementsSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + int count = canvas.SaveLayer(new GraphicsOptions { BlendPercentage = 0.5f }); + Assert.Equal(2, count); + Assert.Equal(2, canvas.SaveCount); + } + + [Fact] + public void SaveLayer_WithBounds_IncrementsSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + int count = canvas.SaveLayer(new GraphicsOptions(), new Rectangle(10, 10, 32, 32)); + Assert.Equal(2, count); + Assert.Equal(2, canvas.SaveCount); + } + + [Fact] + public void SaveLayer_Restore_DecrementsSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.SaveLayer(); + Assert.Equal(2, canvas.SaveCount); + + canvas.Restore(); + Assert.Equal(1, canvas.SaveCount); + } + + [Fact] + public void SaveLayer_RestoreTo_DecrementsSaveCount() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + int before = canvas.SaveCount; + canvas.SaveLayer(); + canvas.Save(); + Assert.Equal(3, canvas.SaveCount); + + canvas.RestoreTo(before); + Assert.Equal(before, canvas.SaveCount); + } + + [Fact] + public void SaveLayer_DrawAndRestore_CompositesLayerOntoTarget() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + // Fill background white. + canvas.Fill(new SolidBrush(Color.White)); + + // SaveLayer with full opacity, draw red rectangle, then restore. + canvas.SaveLayer(); + canvas.Fill(new SolidBrush(Color.Red), new RectangularPolygon(10, 10, 20, 20)); + canvas.Restore(); + + // The red rectangle should be composited onto the white background. + Rgba32 center = target[20, 20]; + Assert.Equal(new Rgba32(255, 0, 0, 255), center); + + // Outside the filled region should remain white. + Rgba32 corner = target[0, 0]; + Assert.Equal(new Rgba32(255, 255, 255, 255), corner); + } + + [Fact] + public void SaveLayer_WithHalfOpacity_CompositesWithBlend() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + // Fill background white. + canvas.Fill(new SolidBrush(Color.White)); + + // SaveLayer with 50% opacity, draw red rectangle, then restore. + canvas.SaveLayer(new GraphicsOptions { BlendPercentage = 0.5f }); + canvas.Fill(new SolidBrush(Color.Red), new RectangularPolygon(10, 10, 20, 20)); + canvas.Restore(); + + // The red should be blended at ~50% onto white, giving approximately (255, 128, 128). + Rgba32 center = target[20, 20]; + Assert.InRange(center.R, 120, 255); + Assert.InRange(center.G, 100, 140); + Assert.InRange(center.B, 100, 140); + } + + [Fact] + public void SaveLayer_Dispose_CompositesActiveLayer() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + + // Create canvas, push a layer, draw, and dispose without explicit Restore. + using (DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions())) + { + canvas.Fill(new SolidBrush(Color.White)); + canvas.SaveLayer(); + canvas.Fill(new SolidBrush(Color.Blue), new RectangularPolygon(0, 0, 32, 32)); + + // Dispose should composite the layer. + } + + // After dispose, the blue fill should be visible. + Rgba32 pixel = target[16, 16]; + Assert.Equal(new Rgba32(0, 0, 255, 255), pixel); + } + + [Fact] + public void SaveLayer_NestedLayers_CompositeCorrectly() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Fill(new SolidBrush(Color.White)); + + // Outer layer. + canvas.SaveLayer(); + canvas.Fill(new SolidBrush(Color.Red), new RectangularPolygon(0, 0, 64, 64)); + + // Inner layer. + canvas.SaveLayer(); + canvas.Fill(new SolidBrush(Color.Blue), new RectangularPolygon(16, 16, 32, 32)); + canvas.Restore(); // Composites blue onto red. + + canvas.Restore(); // Composites red+blue onto white. + + // Center should be blue (inner layer overwrites outer). + Rgba32 center = target[32, 32]; + Assert.Equal(new Rgba32(0, 0, 255, 255), center); + + // Corner should be red (outer layer only). + Rgba32 corner = target[5, 5]; + Assert.Equal(new Rgba32(255, 0, 0, 255), corner); + } + + [Fact] + public void SaveLayer_MixedSaveAndSaveLayer_WorksCorrectly() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + canvas.Fill(new SolidBrush(Color.White)); + + canvas.Save(); // SaveCount = 2 (plain save) + canvas.SaveLayer(); // SaveCount = 3 (layer) + canvas.Save(); // SaveCount = 4 (plain save) + Assert.Equal(4, canvas.SaveCount); + + canvas.Fill(new SolidBrush(Color.Green), new RectangularPolygon(0, 0, 64, 64)); + + // RestoreTo(1) should pop all states including the layer. + canvas.RestoreTo(1); + Assert.Equal(1, canvas.SaveCount); + + Rgba32 pixel = target[32, 32]; + Assert.Equal(new Rgba32(0, 128, 0, 255), pixel); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.StrokeOptions.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.StrokeOptions.cs new file mode 100644 index 000000000..a6d0411ca --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.StrokeOptions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(360, 220, PixelTypes.Rgba32)] + public void Draw_NormalizeOutputFalse_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + IPath leftPath = CreateBowTiePath(new RectangleF(28, 34, 128, 152)); + IPath rightPath = CreateBowTiePath(new RectangleF(204, 34, 128, 152)); + + SolidPen pen = new(Color.CornflowerBlue.WithAlpha(0.88F), 24F); + pen.StrokeOptions.LineJoin = LineJoin.Round; + pen.StrokeOptions.LineCap = LineCap.Round; + + DrawingOptions evenOddOptions = new() + { + ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.EvenOdd } + }; + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.GhostWhite.WithAlpha(0.85F)), new Rectangle(12, 12, 336, 196)); + + _ = canvas.Save(evenOddOptions); + canvas.Draw(pen, leftPath); + canvas.Draw(pen, rightPath); + canvas.Restore(); + + canvas.Draw(Pens.Solid(Color.DarkSlateGray, 2F), leftPath); + canvas.Draw(Pens.Solid(Color.DarkSlateGray, 2F), rightPath); + canvas.DrawLine(Pens.DashDot(Color.Gray, 2F), new PointF(180, 20), new PointF(180, 200)); + canvas.Draw(Pens.Solid(Color.Black, 2F), new Rectangle(8, 8, 344, 204)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.0001F), provider, appendSourceFileOrDescription: false); + } + + private static IPath CreateBowTiePath(RectangleF bounds) + { + float left = bounds.Left; + float right = bounds.Right; + float top = bounds.Top; + float bottom = bounds.Bottom; + + PathBuilder builder = new(); + builder.AddLine(left, top, right, bottom); + builder.AddLine(right, bottom, left, bottom); + builder.AddLine(left, bottom, right, top); + builder.AddLine(right, top, left, top); + builder.CloseAllFigures(); + return builder.Build(); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Text.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Text.cs new file mode 100644 index 000000000..ed523dff9 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Text.cs @@ -0,0 +1,253 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.Fonts; +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Text; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithSolidFilledImages(492, 360, nameof(Color.White), PixelTypes.Rgba32, ColorFontSupport.ColrV1)] + [WithSolidFilledImages(492, 360, nameof(Color.White), PixelTypes.Rgba32, ColorFontSupport.Svg)] + public void DrawGlyphs_EmojiFont_MatchesReference(TestImageProvider provider, ColorFontSupport support) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.NotoColorEmojiRegular, 100); + Font fallback = TestFontUtilities.GetFont(TestFonts.OpenSans, 100); + const string text = "a😨 b😅\r\nc🥲 d🤩"; + + RichTextOptions textOptions = new(font) + { + ColorFontSupport = support, + LineSpacing = 1.8F, + FallbackFontFamilies = [fallback.Family], + TextRuns = + [ + new RichTextRun + { + Start = 0, + End = text.GetGraphemeCount(), + TextDecorations = TextDecorations.Strikeout | TextDecorations.Underline | TextDecorations.Overline + } + ] + }; + + IReadOnlyList glyphs = TextBuilder.GenerateGlyphs(text, textOptions); + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.DrawGlyphs(Brushes.Solid(Color.Black), Pens.Solid(Color.Black, 2F), glyphs); + canvas.Flush(); + + target.DebugSave(provider, $"{support}-draw-glyphs", appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, $"{support}-draw-glyphs", appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(760, 320, PixelTypes.Rgba32)] + public void DrawText_Multiline_WithLineMetricsGuides_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + + DrawingOptions options = new() + { + Transform = Matrix4x4.CreateTranslation(24F, 22F, 0) + }; + + using DrawingCanvas canvas = CreateCanvas(provider, target, options); + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 32); + + string text = "Quick wafting zephyrs vex bold Jim.\n" + + "How quickly daft jumping zebras vex.\n" + + "Sphinx of black quartz, judge my vow."; + + RichTextOptions textOptions = new(font) + { + Origin = PointF.Empty, + LineSpacing = 1.45F + }; + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.LightSteelBlue.WithAlpha(0.25F)), new Rectangle(0, 0, 712, 276)); + canvas.DrawText(textOptions, text, Brushes.Solid(Color.Black), pen: null); + + LineMetrics[] lineMetrics = canvas.GetTextLineMetrics(textOptions, text); + float lineOriginY = textOptions.Origin.Y; + for (int i = 0; i < lineMetrics.Length; i++) + { + LineMetrics metrics = lineMetrics[i]; + float startX = metrics.Start; + float endX = metrics.Start + metrics.Extent; + float topY = lineOriginY; + float ascenderY = lineOriginY + metrics.Ascender; + float baselineY = lineOriginY + metrics.Baseline; + float descenderY = lineOriginY + metrics.Descender; + float lineHeightY = lineOriginY + metrics.LineHeight; + + canvas.DrawLine(Pens.Solid(Color.DimGray.WithAlpha(0.8F), 1), new PointF(startX, topY), new PointF(endX, topY)); + canvas.DrawLine(Pens.Solid(Color.RoyalBlue.WithAlpha(0.9F), 1), new PointF(startX, ascenderY), new PointF(endX, ascenderY)); + canvas.DrawLine(Pens.Solid(Color.Crimson.WithAlpha(0.9F), 1), new PointF(startX, baselineY), new PointF(endX, baselineY)); + canvas.DrawLine(Pens.Solid(Color.DarkOrange.WithAlpha(0.9F), 1), new PointF(startX, descenderY), new PointF(endX, descenderY)); + canvas.DrawLine(Pens.Solid(Color.SeaGreen.WithAlpha(0.9F), 1), new PointF(startX, lineHeightY), new PointF(endX, lineHeightY)); + canvas.DrawLine(Pens.Solid(Color.DimGray.WithAlpha(0.8F), 1), new PointF(startX, topY), new PointF(startX, lineHeightY)); + canvas.DrawLine(Pens.Solid(Color.DimGray.WithAlpha(0.8F), 1), new PointF(endX, topY), new PointF(endX, lineHeightY)); + + lineOriginY += metrics.LineHeight; + } + + canvas.Draw(Pens.Solid(Color.Black, 2), new Rectangle(0, 0, 712, 276)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(420, 220, PixelTypes.Rgba32)] + public void DrawText_FillAndStroke_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation(-0.08F, new Vector2(210, 110))) + }; + + using DrawingCanvas canvas = CreateCanvas(provider, target, options); + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 36); + RichTextOptions textOptions = new(font) + { + Origin = new PointF(24, 36), + WrappingLength = 372 + }; + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.DrawText( + textOptions, + "Canvas text\nwith fill + stroke", + Brushes.Solid(Color.MidnightBlue.WithAlpha(0.82F)), + Pens.Solid(Color.Gold, 2F)); + canvas.Draw(Pens.Solid(Color.DimGray, 3), new Rectangle(10, 10, 400, 200)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(320, 180, PixelTypes.Rgba32)] + public void DrawText_PenOnly_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 52); + RichTextOptions textOptions = new(font) + { + Origin = new PointF(18, 42) + }; + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.LightSkyBlue.WithAlpha(0.45F)), new Rectangle(12, 14, 296, 152)); + canvas.DrawText(textOptions, "OUTLINE", brush: null, pen: Pens.Solid(Color.SeaGreen, 3.5F)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(360, 220, PixelTypes.Rgba32)] + public void DrawText_AlongPathWithOrigin_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + IPath textPath = new EllipsePolygon(new PointF(172, 112), new SizeF(246, 112)); + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 21); + RichTextOptions textOptions = new(font) + { + Path = textPath, + Origin = new PointF(16, -10), + WrappingLength = textPath.ComputeLength(), + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Bottom + }; + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Draw(Pens.Solid(Color.SlateGray, 2), textPath); + canvas.DrawText( + textOptions, + "Sphinx of black quartz, judge my vow.", + Brushes.Solid(Color.DarkRed.WithAlpha(0.9F)), + pen: null); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(840, 420, PixelTypes.Rgba32)] + public void DrawText_WithWrappingAlignmentAndLineSpacing_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 28); + Rectangle layoutBounds = new(120, 50, 600, 320); + + RichTextOptions textOptions = new(font) + { + Origin = new PointF( + layoutBounds.Left + (layoutBounds.Width / 2F), + layoutBounds.Top + (layoutBounds.Height / 2F)), + WrappingLength = layoutBounds.Width - 64F, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + LineSpacing = 2.1F + }; + + string text = + "Pack my box with five dozen liquor jugs while zephyrs drift across the bay.\n" + + "Sphinx of black quartz, judge my vow."; + + canvas.Clear(Brushes.Solid(Color.White)); + canvas.Fill(Brushes.Solid(Color.LightGoldenrodYellow.WithAlpha(0.45F)), layoutBounds); + canvas.Draw(Pens.Solid(Color.SlateGray, 2F), layoutBounds); + canvas.DrawLine( + Pens.Dash(Color.Gray.WithAlpha(0.8F), 1.5F), + new PointF(textOptions.Origin.X, layoutBounds.Top), + new PointF(textOptions.Origin.X, layoutBounds.Bottom)); + canvas.DrawLine( + Pens.Dash(Color.Gray.WithAlpha(0.8F), 1.5F), + new PointF(layoutBounds.Left, textOptions.Origin.Y), + new PointF(layoutBounds.Right, textOptions.Origin.Y)); + + canvas.DrawText( + textOptions, + text, + Brushes.Solid(Color.DarkBlue.WithAlpha(0.86F)), + Pens.Solid(Color.DarkRed.WithAlpha(0.55F), 1.1F)); + + canvas.Draw(Pens.Solid(Color.Black, 3F), new Rectangle(10, 10, 820, 400)); + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.TextMeasuring.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.TextMeasuring.cs new file mode 100644 index 000000000..15f69e270 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.TextMeasuring.cs @@ -0,0 +1,285 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class DrawingCanvasTests +{ + [Theory] + [WithBlankImage(600, 400, PixelTypes.Rgba32)] + public void TextMeasuring_RenderedMetrics_MatchesReference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image target = provider.GetImage(); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 36); + const string text = "Sphinx of black quartz,\njudge my vow."; + + RichTextOptions textOptions = new(font) + { + Origin = new PointF(60, 60), + LineSpacing = 1.8F + }; + + canvas.Clear(Brushes.Solid(Color.White)); + + PointF origin = textOptions.Origin; + + // Line metrics: colored bands with ascender/baseline/descender guides. + int lineCount = canvas.CountTextLines(textOptions, text); + LineMetrics[] lineMetrics = canvas.GetTextLineMetrics(textOptions, text); + Assert.Equal(lineCount, lineMetrics.Length); + + float lineOriginY = origin.Y; + Color[] bandColors = + [ + Color.LightCoral.WithAlpha(0.4F), + Color.Khaki.WithAlpha(0.6F), + Color.LightGreen.WithAlpha(0.4F), + ]; + + for (int i = 0; i < lineMetrics.Length; i++) + { + LineMetrics metrics = lineMetrics[i]; + float startX = origin.X + metrics.Start; + float endX = startX + metrics.Extent; + + canvas.Fill( + Brushes.Solid(bandColors[i % bandColors.Length]), + new RectangularPolygon(startX, lineOriginY, endX - startX, metrics.LineHeight)); + + canvas.DrawLine( + Pens.Solid(Color.Teal.WithAlpha(0.9F), 1.5F), + new PointF(startX, lineOriginY + metrics.Ascender), + new PointF(endX, lineOriginY + metrics.Ascender)); + + canvas.DrawLine( + Pens.Solid(Color.Crimson.WithAlpha(0.9F), 1.5F), + new PointF(startX, lineOriginY + metrics.Baseline), + new PointF(endX, lineOriginY + metrics.Baseline)); + + canvas.DrawLine( + Pens.Solid(Color.DarkOrange.WithAlpha(0.9F), 1.5F), + new PointF(startX, lineOriginY + metrics.Descender), + new PointF(endX, lineOriginY + metrics.Descender)); + + lineOriginY += metrics.LineHeight; + } + + // Character renderable bounds: outlined rectangles positioned at each glyph. + if (canvas.TryMeasureCharacterRenderableBounds(textOptions, text, out ReadOnlySpan charRenderableBounds)) + { + Color[] renderableColors = + [ + Color.Black, + Color.Black + ]; + + for (int i = 0; i < charRenderableBounds.Length; i++) + { + FontRectangle rb = charRenderableBounds[i].Bounds; + canvas.Draw( + Pens.Solid(renderableColors[i % renderableColors.Length], 1), + new RectangularPolygon(rb.X, rb.Y, rb.Width, rb.Height)); + } + } + + // Character bounds: alternating filled rectangles behind the glyphs. + if (canvas.TryMeasureCharacterBounds(textOptions, text, out ReadOnlySpan charBounds)) + { + Color[] charColors = + [ + Color.Gold.WithAlpha(0.5F), + Color.MediumPurple.WithAlpha(0.5F), + ]; + + for (int i = 0; i < charBounds.Length; i++) + { + FontRectangle b = charBounds[i].Bounds; + canvas.Fill( + Brushes.Solid(charColors[i % charColors.Length]), + new RectangularPolygon(b.X, b.Y, b.Width, b.Height)); + } + } + + // Render the text. + canvas.DrawText(textOptions, text, Brushes.Solid(Color.Black), pen: null); + + // Advance rectangle (green outline). + RectangleF advance = canvas.MeasureTextAdvance(textOptions, text); + canvas.Draw( + Pens.Solid(Color.SeaGreen, 2), + new RectangularPolygon(origin.X + advance.X, origin.Y + advance.Y, advance.Width, advance.Height)); + + // Bounds rectangle (dodger blue outline). + RectangleF bounds = canvas.MeasureTextBounds(textOptions, text); + canvas.Draw( + Pens.Solid(Color.DodgerBlue, 2), + new RectangularPolygon(bounds.X, bounds.Y, bounds.Width, bounds.Height)); + + // Renderable bounds rectangle (black outline). + RectangleF renderableBounds = canvas.MeasureTextRenderableBounds(textOptions, text); + canvas.Draw( + Pens.Solid(Color.Black, 2), + new RectangularPolygon(renderableBounds.X, renderableBounds.Y, renderableBounds.Width, renderableBounds.Height)); + + // Origin crosshair. + canvas.DrawLine(Pens.Solid(Color.Gray, 1), new PointF(origin.X - 12, origin.Y), new PointF(origin.X + 12, origin.Y)); + canvas.DrawLine(Pens.Solid(Color.Gray, 1), new PointF(origin.X, origin.Y - 12), new PointF(origin.X, origin.Y + 12)); + + // Key. + Font keyFont = TestFontUtilities.GetFont(TestFonts.OpenSans, 13); + float keyX = 16; + float keyY = 280; + const float swatchW = 24; + const float swatchH = 12; + const float rowHeight = 20; + const float labelOffset = swatchW + 6; + + (string Label, Color Color1, Color? Color2, bool IsFill)[] keyEntries = + [ + ("Advance", Color.SeaGreen, null, false), + ("Bounds", Color.DodgerBlue, null, false), + ("Renderable Bounds", Color.Black, null, false), + ("Ascender", Color.Teal.WithAlpha(0.9F), null, true), + ("Baseline", Color.Crimson.WithAlpha(0.9F), null, true), + ("Descender", Color.DarkOrange.WithAlpha(0.9F), null, true), + ("Char Bounds", Color.Gold.WithAlpha(0.5F), Color.MediumPurple.WithAlpha(0.5F), true), + ("Char Renderable Bounds", Color.Black, null, false), + ("Line Band", Color.LightCoral.WithAlpha(0.4F), Color.Khaki.WithAlpha(0.6F), true), + ("Origin", Color.Gray, null, false), + ]; + + for (int i = 0; i < keyEntries.Length; i++) + { + float col = i < 5 ? 0 : 300; + float row = i < 5 ? i : i - 5; + float x = keyX + col; + float y = keyY + (row * rowHeight); + float halfW = swatchW / 2F; + + if (keyEntries[i].IsFill) + { + if (keyEntries[i].Color2 is Color c2) + { + canvas.Fill( + Brushes.Solid(keyEntries[i].Color1), + new RectangularPolygon(x, y, halfW, swatchH)); + canvas.Fill( + Brushes.Solid(c2), + new RectangularPolygon(x + halfW, y, halfW, swatchH)); + } + else + { + canvas.Fill( + Brushes.Solid(keyEntries[i].Color1), + new RectangularPolygon(x, y, swatchW, swatchH)); + } + } + else + { + canvas.Draw( + Pens.Solid(keyEntries[i].Color1, 2), + new RectangularPolygon(x, y, swatchW, swatchH)); + } + + RichTextOptions keyTextOptions = new(keyFont) { Origin = new PointF(x + labelOffset, y - 1) }; + canvas.DrawText(keyTextOptions, keyEntries[i].Label, Brushes.Solid(Color.Black), pen: null); + } + + canvas.Flush(); + + target.DebugSave(provider, appendSourceFileOrDescription: false); + target.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Fact] + public void MeasureTextSize_ReturnsNonEmptyRectangle() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 24); + RichTextOptions textOptions = new(font) { Origin = new PointF(0, 0) }; + + RectangleF size = canvas.MeasureTextSize(textOptions, "Hello"); + + Assert.True(size.Width > 0, "Width should be positive."); + Assert.True(size.Height > 0, "Height should be positive."); + } + + [Fact] + public void MeasureTextSize_EmptyText_ReturnsEmpty() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 24); + RichTextOptions textOptions = new(font) { Origin = new PointF(0, 0) }; + + RectangleF size = canvas.MeasureTextSize(textOptions, ReadOnlySpan.Empty); + + Assert.Equal(RectangleF.Empty, size); + } + + [Fact] + public void MeasureTextSize_LongerText_IsWider() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 24); + RichTextOptions textOptions = new(font) { Origin = new PointF(0, 0) }; + + RectangleF shortSize = canvas.MeasureTextSize(textOptions, "Hi"); + RectangleF longSize = canvas.MeasureTextSize(textOptions, "Hello World"); + + Assert.True(longSize.Width > shortSize.Width, "Longer text should produce a wider measurement."); + } + + [Fact] + public void TryMeasureCharacterAdvances_ReturnsAdvancesForEachCharacter() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 24); + RichTextOptions textOptions = new(font) { Origin = new PointF(0, 0) }; + + const string text = "ABC"; + bool result = canvas.TryMeasureCharacterAdvances(textOptions, text, out ReadOnlySpan advances); + + Assert.True(result); + Assert.Equal(text.Length, advances.Length); + + for (int i = 0; i < advances.Length; i++) + { + Assert.True(advances[i].Bounds.Width > 0, $"Advance width for character {i} should be positive."); + } + } + + [Fact] + public void TryMeasureCharacterAdvances_EmptyText_ReturnsFalse() + { + TestImageProvider provider = TestImageProvider.Blank(1, 1); + using Image target = new(64, 64); + using DrawingCanvas canvas = CreateCanvas(provider, target, new DrawingOptions()); + + Font font = TestFontUtilities.GetFont(TestFonts.OpenSans, 24); + RichTextOptions textOptions = new(font) { Origin = new PointF(0, 0) }; + + bool result = canvas.TryMeasureCharacterAdvances(textOptions, ReadOnlySpan.Empty, out ReadOnlySpan advances); + + Assert.False(result); + Assert.True(advances.IsEmpty); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.cs new file mode 100644 index 000000000..d134819a2 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +[GroupOutput("Drawing")] +public partial class DrawingCanvasTests +{ + private static DrawingCanvas CreateCanvas( + TestImageProvider provider, + Image image, + DrawingOptions options) + where TPixel : unmanaged, IPixel + => new( + provider.Configuration, + image.Frames.RootFrame.PixelBuffer.GetRegion(), + options); + + private static PathBuilder CreateClosedPathBuilder() + { + PathBuilder pathBuilder = new(); + pathBuilder.AddLine(22, 24, 124, 30); + pathBuilder.AddLine(124, 30, 168, 98); + pathBuilder.AddLine(168, 98, 40, 108); + pathBuilder.AddLine(40, 108, 22, 24); + pathBuilder.CloseAllFigures(); + return pathBuilder; + } + + private static PathBuilder CreateOpenPathBuilder() + { + PathBuilder pathBuilder = new(); + pathBuilder.AddLine(20, 98, 54, 22); + pathBuilder.AddLine(54, 22, 114, 76); + pathBuilder.AddLine(114, 76, 170, 26); + return pathBuilder; + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/FillPathProcessorTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/FillPathProcessorTests.cs deleted file mode 100644 index 0d49fa0e4..000000000 --- a/tests/ImageSharp.Drawing.Tests/Processing/FillPathProcessorTests.cs +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Reflection; -using Moq; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing; -using SixLabors.ImageSharp.Drawing.Shapes; -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; - -namespace SixLabors.ImageSharp.Drawing.Tests.Processing; - -public class FillPathProcessorTests -{ - [Fact] - public void FillOffCanvas() - { - Rectangle bounds = new(-100, -10, 10, 10); - - // Specifically not using RectangularPolygon here to ensure the FillPathProcessor is used. - LinearLineSegment[] points = - [ - new(new PointF(bounds.Left, bounds.Top), new PointF(bounds.Right, bounds.Top)), - new(new PointF(bounds.Right, bounds.Top), new PointF(bounds.Right, bounds.Bottom)), - new(new PointF(bounds.Right, bounds.Bottom), new PointF(bounds.Left, bounds.Bottom)), - new(new PointF(bounds.Left, bounds.Bottom), new PointF(bounds.Left, bounds.Top)) - ]; - Path path = new(points); - Mock brush = new(); - GraphicsOptions options = new() { Antialias = true }; - FillPathProcessor processor = new(new DrawingOptions() { GraphicsOptions = options }, brush.Object, path); - Image img = new(10, 10); - processor.Execute(img.Configuration, img, bounds); - } - - [Fact] - public void DrawOffCanvas() - { - using (Image img = new(10, 10)) - { - img.Mutate(x => x.DrawLine( - new SolidPen(Color.Black, 10), - new Vector2(-10, 5), - new Vector2(20, 5))); - } - } - - [Fact] - public void OtherShape() - { - Rectangle imageSize = new(0, 0, 500, 500); - EllipsePolygon path = new(1, 1, 23); - FillPathProcessor processor = new( - new DrawingOptions() - { - GraphicsOptions = { Antialias = true } - }, - Brushes.Solid(Color.Red), - path); - - IImageProcessor pixelProcessor = processor.CreatePixelSpecificProcessor(null, null, imageSize); - - Assert.IsType>(pixelProcessor); - } - - [Fact] - public void RectangleFloatAndAntialias() - { - Rectangle imageSize = new(0, 0, 500, 500); - RectangleF floatRect = new(10.5f, 10.5f, 400.6f, 400.9f); - Rectangle expectedRect = new(10, 10, 400, 400); - RectangularPolygon path = new(floatRect); - FillPathProcessor processor = new( - new DrawingOptions() - { - GraphicsOptions = { Antialias = true } - }, - Brushes.Solid(Color.Red), - path); - - IImageProcessor pixelProcessor = processor.CreatePixelSpecificProcessor(null, null, imageSize); - - Assert.IsType>(pixelProcessor); - } - - [Fact] - public void IntRectangle() - { - Rectangle imageSize = new(0, 0, 500, 500); - Rectangle expectedRect = new(10, 10, 400, 400); - RectangularPolygon path = new(expectedRect); - FillPathProcessor processor = new( - new DrawingOptions() - { - GraphicsOptions = { Antialias = true } - }, - Brushes.Solid(Color.Red), - path); - - IImageProcessor pixelProcessor = processor.CreatePixelSpecificProcessor(null, null, imageSize); - - FillProcessor fill = Assert.IsType>(pixelProcessor); - Assert.Equal(expectedRect, fill.GetProtectedValue("SourceRectangle")); - } - - [Fact] - public void FloatRectAntialiasingOff() - { - Rectangle imageSize = new(0, 0, 500, 500); - RectangleF floatRect = new(10.5f, 10.5f, 400.6f, 400.9f); - Rectangle expectedRect = new(10, 10, 400, 400); - RectangularPolygon path = new(floatRect); - FillPathProcessor processor = new( - new DrawingOptions() - { - GraphicsOptions = { Antialias = false } - }, - Brushes.Solid(Color.Red), - path); - - IImageProcessor pixelProcessor = processor.CreatePixelSpecificProcessor(null, null, imageSize); - FillProcessor fill = Assert.IsType>(pixelProcessor); - - Assert.Equal(expectedRect, fill.GetProtectedValue("SourceRectangle")); - } - - [Fact] - public void DoesNotThrowForIssue928() - { - RectangleF rectText = new(0, 0, 2000, 2000); - using (Image img = new((int)rectText.Width, (int)rectText.Height)) - { - img.Mutate(x => x.Fill(Color.Transparent)); - - img.Mutate( - ctx => ctx.DrawLine( - Color.Red, - 0.984252f, - new PointF(104.762581f, 1074.99365f), - new PointF(104.758667f, 1075.01721f), - new PointF(104.757675f, 1075.04114f), - new PointF(104.759628f, 1075.065f), - new PointF(104.764488f, 1075.08838f), - new PointF(104.772186f, 1075.111f), - new PointF(104.782608f, 1075.13245f), - new PointF(104.782608f, 1075.13245f))); - } - } - - [Fact] - public void DoesNotThrowFillingTriangle() - { - using (Image image = new(28, 28)) - { - Polygon path = new( - new LinearLineSegment(new PointF(17.11f, 13.99659f), new PointF(14.01433f, 27.06201f)), - new LinearLineSegment(new PointF(14.01433f, 27.06201f), new PointF(13.79267f, 14.00023f)), - new LinearLineSegment(new PointF(13.79267f, 14.00023f), new PointF(17.11f, 13.99659f))); - - image.Mutate(ctx => ctx.Fill(Color.White, path)); - } - } - - [Fact] - public void DrawPathProcessor_UsesNonZeroRule_WhenStrokeNormalizationIsDisabled() - { - DrawingOptions options = new() - { - ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.EvenOdd } - }; - - SolidPen pen = new(Color.Black, 3F) - { - StrokeOptions = { NormalizeOutput = false } - }; - - DrawPathProcessor processor = new(options, pen, new RectangularPolygon(2F, 2F, 8F, 8F)); - - using Image image = new(20, 20); - IImageProcessor pixelProcessor = - processor.CreatePixelSpecificProcessor(image.Configuration, image, image.Bounds); - - FillPathProcessor fillProcessor = Assert.IsType>(pixelProcessor); - FillPathProcessor definition = fillProcessor.GetPrivateFieldValue("definition"); - - Assert.Equal(IntersectionRule.NonZero, definition.Options.ShapeOptions.IntersectionRule); - } - - [Fact] - public void DrawPathProcessor_PreservesRule_WhenStrokeNormalizationIsEnabled() - { - DrawingOptions options = new() - { - ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.EvenOdd } - }; - - SolidPen pen = new(Color.Black, 3F) - { - StrokeOptions = { NormalizeOutput = true } - }; - - DrawPathProcessor processor = new(options, pen, new RectangularPolygon(2F, 2F, 8F, 8F)); - - using Image image = new(20, 20); - IImageProcessor pixelProcessor = - processor.CreatePixelSpecificProcessor(image.Configuration, image, image.Bounds); - - FillPathProcessor fillProcessor = Assert.IsType>(pixelProcessor); - FillPathProcessor definition = fillProcessor.GetPrivateFieldValue("definition"); - - Assert.Equal(IntersectionRule.EvenOdd, definition.Options.ShapeOptions.IntersectionRule); - } - - [Fact] - public void FillPathProcessor_UsesConfiguredRasterizer() - { - RecordingRasterizer rasterizer = new(); - Configuration configuration = new(); - configuration.SetRasterizer(rasterizer); - - FillPathProcessor processor = new( - new DrawingOptions(), - Brushes.Solid(Color.White), - new EllipsePolygon(6F, 6F, 4F)); - - using Image image = new(configuration, 20, 20); - processor.Execute(configuration, image, image.Bounds); - - Assert.True(rasterizer.CallCount > 0); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void FillPathProcessor_UsesExpectedRasterizationModeAndPixelBoundarySamplingOrigin(bool antialias) - { - RecordingRasterizer rasterizer = new(); - Configuration configuration = new(); - configuration.SetRasterizer(rasterizer); - - DrawingOptions drawingOptions = new() - { - GraphicsOptions = new GraphicsOptions - { - Antialias = antialias - } - }; - - FillPathProcessor processor = new( - drawingOptions, - Brushes.Solid(Color.White), - new EllipsePolygon(6F, 6F, 4F)); - - using Image image = new(configuration, 20, 20); - processor.Execute(configuration, image, image.Bounds); - - RasterizationMode expectedMode = antialias ? RasterizationMode.Antialiased : RasterizationMode.Aliased; - Assert.Equal(expectedMode, rasterizer.LastRasterizationMode); - Assert.Equal(RasterizerSamplingOrigin.PixelBoundary, rasterizer.LastSamplingOrigin); - } - - private sealed class RecordingRasterizer : IRasterizer - { - public int CallCount { get; private set; } - - public RasterizationMode LastRasterizationMode { get; private set; } - - public RasterizerSamplingOrigin LastSamplingOrigin { get; private set; } - - public void Rasterize( - IPath path, - in RasterizerOptions options, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct - { - this.CallCount++; - this.LastRasterizationMode = options.RasterizationMode; - this.LastSamplingOrigin = options.SamplingOrigin; - } - } -} - -internal static class ReflectionHelpers -{ - internal static T GetProtectedValue(this object obj, string name) - => (T)obj.GetType() - .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) - .Single(x => x.Name == name) - .GetValue(obj); - - internal static T GetPrivateFieldValue(this object obj, string name) - => (T)obj.GetType() - .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) - .Single(x => x.Name == name) - .GetValue(obj); -} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ImageOperationTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/ImageOperationTests.cs index fb141083f..a3d68f075 100644 --- a/tests/ImageSharp.Drawing.Tests/Processing/ImageOperationTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Processing/ImageOperationTests.cs @@ -108,7 +108,11 @@ public void ApplyProcessors_ListOfProcessors_AppliesAllProcessorsToOperation() Assert.Contains(this.processorDefinition, operations.Applied.Select(x => x.NonGenericProcessor)); } - public void Dispose() => this.image.Dispose(); + public void Dispose() + { + this.image.Dispose(); + GC.SuppressFinalize(this); + } [Fact] public void GenericMutate_WhenDisposed_Throws() diff --git a/tests/ImageSharp.Drawing.Tests/Processing/PenTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/PenTests.cs new file mode 100644 index 000000000..6486159a8 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/PenTests.cs @@ -0,0 +1,324 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public class PenTests +{ + // Constructor / property tests + [Fact] + public void SolidPen_ColorConstructor_SetsProperties() + { + SolidPen pen = new(Color.Red); + + Assert.Equal(1, pen.StrokeWidth); + Assert.IsType(pen.StrokeFill); + Assert.True(pen.StrokePattern.IsEmpty); + } + + [Fact] + public void SolidPen_ColorWidthConstructor_SetsProperties() + { + SolidPen pen = new(Color.Blue, 5); + + Assert.Equal(5, pen.StrokeWidth); + Assert.IsType(pen.StrokeFill); + Assert.True(pen.StrokePattern.IsEmpty); + } + + [Fact] + public void SolidPen_BrushConstructor_SetsProperties() + { + Brush brush = Brushes.Solid(Color.Green); + SolidPen pen = new(brush); + + Assert.Equal(1, pen.StrokeWidth); + Assert.Same(brush, pen.StrokeFill); + Assert.True(pen.StrokePattern.IsEmpty); + } + + [Fact] + public void SolidPen_BrushWidthConstructor_SetsProperties() + { + Brush brush = Brushes.Solid(Color.Green); + SolidPen pen = new(brush, 7.5F); + + Assert.Equal(7.5F, pen.StrokeWidth); + Assert.Same(brush, pen.StrokeFill); + Assert.True(pen.StrokePattern.IsEmpty); + } + + [Fact] + public void SolidPen_PenOptionsConstructor_SetsProperties() + { + PenOptions options = new(Color.Coral, 4) + { + StrokeOptions = new StrokeOptions { LineJoin = LineJoin.Round } + }; + + SolidPen pen = new(options); + + Assert.Equal(4, pen.StrokeWidth); + Assert.Equal(LineJoin.Round, pen.StrokeOptions.LineJoin); + } + + [Fact] + public void PatternPen_ColorPatternConstructor_SetsProperties() + { + float[] pattern = [3f, 1f]; + PatternPen pen = new(Color.Black, pattern); + + Assert.Equal(1, pen.StrokeWidth); + Assert.IsType(pen.StrokeFill); + Assert.True(pen.StrokePattern.Span.SequenceEqual(pattern)); + } + + [Fact] + public void PatternPen_ColorWidthPatternConstructor_SetsProperties() + { + float[] pattern = [2f, 1f, 1f, 1f]; + PatternPen pen = new(Color.Navy, 3, pattern); + + Assert.Equal(3, pen.StrokeWidth); + Assert.IsType(pen.StrokeFill); + Assert.True(pen.StrokePattern.Span.SequenceEqual(pattern)); + } + + [Fact] + public void PatternPen_BrushWidthPatternConstructor_SetsProperties() + { + Brush brush = Brushes.Solid(Color.Teal); + float[] pattern = [1f, 1f]; + PatternPen pen = new(brush, 2.5F, pattern); + + Assert.Equal(2.5F, pen.StrokeWidth); + Assert.Same(brush, pen.StrokeFill); + Assert.True(pen.StrokePattern.Span.SequenceEqual(pattern)); + } + + [Fact] + public void PatternPen_PenOptionsConstructor_SetsProperties() + { + float[] pattern = [3f, 1f, 1f, 1f]; + PenOptions options = new(Color.Red, 6, pattern) + { + StrokeOptions = new StrokeOptions { LineCap = LineCap.Round } + }; + + PatternPen pen = new(options); + + Assert.Equal(6, pen.StrokeWidth); + Assert.Equal(LineCap.Round, pen.StrokeOptions.LineCap); + Assert.True(pen.StrokePattern.Span.SequenceEqual(pattern)); + } + + [Fact] + public void SolidPen_DefaultStrokeOptions_UsesDefaults() + { + SolidPen pen = new(Color.Black, 2); + + Assert.Equal(LineJoin.Bevel, pen.StrokeOptions.LineJoin); + Assert.Equal(LineCap.Butt, pen.StrokeOptions.LineCap); + Assert.Equal(InnerJoin.Miter, pen.StrokeOptions.InnerJoin); + Assert.Equal(4D, pen.StrokeOptions.MiterLimit); + } + + [Fact] + public void Pens_Dash_HasExpectedPattern() + { + PatternPen pen = Pens.Dash(Color.Black, 1); + + Assert.True(pen.StrokePattern.Span.SequenceEqual(new float[] { 3f, 1f })); + } + + [Fact] + public void Pens_Dot_HasExpectedPattern() + { + PatternPen pen = Pens.Dot(Color.Black, 1); + + Assert.True(pen.StrokePattern.Span.SequenceEqual(new float[] { 1f, 1f })); + } + + [Fact] + public void Pens_DashDot_HasExpectedPattern() + { + PatternPen pen = Pens.DashDot(Color.Black, 1); + + Assert.True(pen.StrokePattern.Span.SequenceEqual(new float[] { 3f, 1f, 1f, 1f })); + } + + [Fact] + public void Pens_DashDotDot_HasExpectedPattern() + { + PatternPen pen = Pens.DashDotDot(Color.Black, 1); + + Assert.True(pen.StrokePattern.Span.SequenceEqual(new float[] { 3f, 1f, 1f, 1f, 1f, 1f })); + } + + // Equality tests + [Fact] + public void SolidPen_Equal_WhenSameColorAndWidth() + { + Pen a = Pens.Solid(Color.Red, 3); + Pen b = Pens.Solid(Color.Red, 3); + + Assert.True(a.Equals(b)); + Assert.True(b.Equals(a)); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void SolidPen_NotEqual_WhenDifferentColor() + { + Pen a = Pens.Solid(Color.Red, 3); + Pen b = Pens.Solid(Color.Blue, 3); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void SolidPen_NotEqual_WhenDifferentWidth() + { + Pen a = Pens.Solid(Color.Red, 3); + Pen b = Pens.Solid(Color.Red, 5); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void PatternPen_Equal_WhenSamePattern() + { + Pen a = Pens.Dash(Color.Black, 2); + Pen b = Pens.Dash(Color.Black, 2); + + Assert.True(a.Equals(b)); + Assert.True(b.Equals(a)); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void PatternPen_NotEqual_WhenDifferentPattern() + { + Pen a = Pens.Dash(Color.Black, 2); + Pen b = Pens.Dot(Color.Black, 2); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void PatternPen_NotEqual_WhenDifferentColor() + { + Pen a = Pens.Dash(Color.Red, 2); + Pen b = Pens.Dash(Color.Green, 2); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void PatternPen_NotEqual_WhenDifferentWidth() + { + Pen a = Pens.Dash(Color.Black, 2); + Pen b = Pens.Dash(Color.Black, 4); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void DashDot_Equal_WhenSameParameters() + { + Pen a = Pens.DashDot(Color.Navy, 3); + Pen b = Pens.DashDot(Color.Navy, 3); + + Assert.True(a.Equals(b)); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void DashDotDot_Equal_WhenSameParameters() + { + Pen a = Pens.DashDotDot(Color.Teal, 1.5F); + Pen b = Pens.DashDotDot(Color.Teal, 1.5F); + + Assert.True(a.Equals(b)); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void DashDot_NotEqual_ToDashDotDot() + { + Pen a = Pens.DashDot(Color.Black, 2); + Pen b = Pens.DashDotDot(Color.Black, 2); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void SolidPen_NotEqual_ToPatternPen() + { + Pen solid = Pens.Solid(Color.Black, 2); + Pen pattern = Pens.Dash(Color.Black, 2); + + Assert.False(solid.Equals(pattern)); + Assert.False(pattern.Equals(solid)); + } + + [Fact] + public void PatternPen_CustomPattern_Equal_WhenSameValues() + { + float[] pattern = [2f, 1f, 0.5f, 1f]; + Pen a = new PatternPen(Color.Red, 3, pattern); + Pen b = new PatternPen(Color.Red, 3, pattern); + + Assert.True(a.Equals(b)); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void PatternPen_CustomPattern_NotEqual_WhenDifferentValues() + { + Pen a = new PatternPen(Color.Red, 3, [2f, 1f, 0.5f, 1f]); + Pen b = new PatternPen(Color.Red, 3, [2f, 1f, 1f, 1f]); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void PatternPen_CustomPattern_NotEqual_WhenDifferentLength() + { + Pen a = new PatternPen(Color.Red, 3, [2f, 1f]); + Pen b = new PatternPen(Color.Red, 3, [2f, 1f, 1f]); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void Pen_Equals_Null_ReturnsFalse() + { + Pen pen = Pens.Dash(Color.Black, 2); + + Assert.False(pen.Equals((Pen?)null)); + Assert.False(pen.Equals((object?)null)); + } + + [Fact] + public void Pen_Equals_Object_WhenSame() + { + Pen a = Pens.Dash(Color.Black, 2); + Pen b = Pens.Dash(Color.Black, 2); + + Assert.True(a.Equals((object)b)); + } + + [Fact] + public void PatternPen_WithBrush_Equal_WhenSameBrushAndPattern() + { + Brush brush = Brushes.Solid(Color.Coral); + Pen a = Pens.Dash(brush, 4); + Pen b = Pens.Dash(brush, 4); + + Assert.True(a.Equals(b)); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithCanvasExtensionsTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithCanvasExtensionsTests.cs new file mode 100644 index 000000000..2d562e01b --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithCanvasExtensionsTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public class ProcessWithCanvasExtensionsTests +{ + [Fact] + public void ProcessWithCanvas_Mutate_AppliesToAllFrames() + { + using Image image = new(24, 16); + image.Frames.AddFrame(image.Frames.RootFrame); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Clear(Brushes.Solid(Color.OrangeRed)))); + + Assert.Equal(Color.OrangeRed.ToPixel(), image.Frames.RootFrame[8, 6]); + Assert.Equal(Color.OrangeRed.ToPixel(), image.Frames[1][8, 6]); + } + + [Fact] + public void ProcessWithCanvas_Clone_AppliesToAllFrames_WithoutMutatingSource() + { + using Image source = new(24, 16); + source.Frames.AddFrame(source.Frames.RootFrame); + source.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Clear(Brushes.Solid(Color.White)))); + + using Image clone = source.Clone( + ctx => ctx.ProcessWithCanvas(canvas => canvas.Clear(Brushes.Solid(Color.MediumPurple)))); + + Assert.Equal(Color.White.ToPixel(), source.Frames.RootFrame[8, 6]); + Assert.Equal(Color.White.ToPixel(), source.Frames[1][8, 6]); + Assert.Equal(Color.MediumPurple.ToPixel(), clone.Frames.RootFrame[8, 6]); + Assert.Equal(Color.MediumPurple.ToPixel(), clone.Frames[1][8, 6]); + } + + [Fact] + public void ProcessWithCanvas_Mutate_DrawImage_AppliesToAllFrames() + { + using Image image = new(24, 16); + image.Frames.AddFrame(image.Frames.RootFrame); + + using Image source = new(8, 8, Color.HotPink.ToPixel()); + + Rectangle sourceRect = new(2, 1, 4, 5); + RectangleF destinationRect = new(6, 4, 10, 6); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Clear(Brushes.Solid(Color.White)); + canvas.DrawImage(source, sourceRect, destinationRect); + })); + + Rgba32 expectedFill = Color.HotPink.ToPixel(); + Rgba32 expectedBackground = Color.White.ToPixel(); + + Assert.Equal(expectedFill, image.Frames.RootFrame[10, 6]); + Assert.Equal(expectedFill, image.Frames[1][10, 6]); + Assert.Equal(expectedBackground, image.Frames.RootFrame[1, 1]); + Assert.Equal(expectedBackground, image.Frames[1][1, 1]); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.AntialiasThreshold.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.AntialiasThreshold.cs new file mode 100644 index 000000000..360d02740 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.AntialiasThreshold.cs @@ -0,0 +1,133 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + [Theory] + [WithSolidFilledImages(100, 100, nameof(Color.Black), PixelTypes.Rgba32)] + public void Fill_AliasedWithDefaultThreshold(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + EllipsePolygon circle = new(50, 50, 40); + DrawingOptions options = new() { GraphicsOptions = new GraphicsOptions { Antialias = false } }; + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(Color.White), circle))); + + int whitePixels = CountPixelsAbove(image, 250); + int partialPixels = CountPixelsBetween(image, 1, 250); + + // Aliased mode should produce no partial-coverage pixels. + Assert.Equal(0, partialPixels); + Assert.True(whitePixels > 0, "Expected some white pixels from the filled circle."); + } + + [Theory] + [WithSolidFilledImages(100, 100, nameof(Color.Black), PixelTypes.Rgba32)] + public void Fill_AliasedLowThreshold_ProducesMorePixelsThanHighThreshold(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + EllipsePolygon circle = new(50, 50, 40); + + DrawingOptions lowOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = false, AntialiasThreshold = 0.1F } + }; + + DrawingOptions highOptions = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = false, AntialiasThreshold = 0.9F } + }; + + using Image lowImage = provider.GetImage(); + lowImage.Mutate(ctx => ctx.ProcessWithCanvas(lowOptions, canvas => canvas.Fill(Brushes.Solid(Color.White), circle))); + int lowCount = CountPixelsAbove(lowImage, 250); + + using Image highImage = provider.GetImage(); + highImage.Mutate(ctx => ctx.ProcessWithCanvas(highOptions, canvas => canvas.Fill(Brushes.Solid(Color.White), circle))); + int highCount = CountPixelsAbove(highImage, 250); + + // A lower threshold includes more edge pixels, so the fill area should be larger. + Assert.True(lowCount > highCount, $"Low threshold ({lowCount} pixels) should produce more pixels than high threshold ({highCount} pixels)."); + } + + [Theory] + [WithSolidFilledImages(100, 100, nameof(Color.Black), PixelTypes.Rgba32)] + public void Fill_AntialiasedIgnoresThreshold(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + EllipsePolygon circle = new(50, 50, 40); + + DrawingOptions options1 = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true, AntialiasThreshold = 0.1F } + }; + + DrawingOptions options2 = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = true, AntialiasThreshold = 0.9F } + }; + + using Image image1 = provider.GetImage(); + image1.Mutate(ctx => ctx.ProcessWithCanvas(options1, canvas => canvas.Fill(Brushes.Solid(Color.White), circle))); + + using Image image2 = provider.GetImage(); + image2.Mutate(ctx => ctx.ProcessWithCanvas(options2, canvas => canvas.Fill(Brushes.Solid(Color.White), circle))); + + // In antialiased mode the threshold is irrelevant; images should be identical. + ImageComparer.Exact.VerifySimilarity(image1, image2); + } + + private static int CountPixelsAbove(Image image, byte threshold) + where TPixel : unmanaged, IPixel + { + int count = 0; + image.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + Rgba32 rgba = row[x].ToRgba32(); + if (rgba.R > threshold) + { + count++; + } + } + } + }); + + return count; + } + + private static int CountPixelsBetween(Image image, byte low, byte high) + where TPixel : unmanaged, IPixel + { + int count = 0; + image.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + Rgba32 rgba = row[x].ToRgba32(); + if (rgba.R >= low && rgba.R < high) + { + count++; + } + } + } + }); + + return count; + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Blending.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Blending.cs new file mode 100644 index 000000000..be13d9094 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Blending.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + public static IEnumerable BlendingsModes { get; } = GetAllModeCombinations(); + + private static IEnumerable GetAllModeCombinations() + { + foreach (object composition in Enum.GetValues(typeof(PixelAlphaCompositionMode))) + { + foreach (object blending in Enum.GetValues(typeof(PixelColorBlendingMode))) + { + yield return [blending, composition]; + } + } + } + + [Theory] + [WithBlankImage(nameof(BlendingsModes), 250, 250, PixelTypes.Rgba32)] + public void BlendingsDarkBlueRectBlendHotPinkRect( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + int scaleX = image.Width / 100; + int scaleY = image.Height / 100; + + DrawingOptions blendOptions = CreateBlendOptions(blending, composition); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.DarkBlue), new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)); + })); + + image.Mutate(ctx => ctx.ProcessWithCanvas(blendOptions, canvas => + { + canvas.Fill(Brushes.Solid(Color.HotPink), new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)); + })); + + VerifyImage(provider, blending, composition, image); + } + + [Theory] + [WithBlankImage(nameof(BlendingsModes), 250, 250, PixelTypes.Rgba32)] + public void BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + int scaleX = image.Width / 100; + int scaleY = image.Height / 100; + + DrawingOptions blendOptions = CreateBlendOptions(blending, composition); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.DarkBlue), new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)); + })); + + image.Mutate(ctx => ctx.ProcessWithCanvas(blendOptions, canvas => + { + canvas.Fill(Brushes.Solid(Color.HotPink), new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)); + })); + + image.Mutate(ctx => ctx.ProcessWithCanvas(blendOptions, canvas => + { + canvas.Fill(Brushes.Solid(Color.Transparent), new EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)); + })); + + VerifyImage(provider, blending, composition, image); + } + + [Theory] + [WithBlankImage(nameof(BlendingsModes), 250, 250, PixelTypes.Rgba32)] + public void BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + int scaleX = image.Width / 100; + int scaleY = image.Height / 100; + + DrawingOptions blendOptions = CreateBlendOptions(blending, composition); + Color transparentRed = Color.Red.WithAlpha(0.5F); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + // Keep legacy shape coordinates identical to the original test. + canvas.Fill(Brushes.Solid(Color.DarkBlue), new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY)); + })); + + image.Mutate(ctx => ctx.ProcessWithCanvas(blendOptions, canvas => + { + canvas.Fill(Brushes.Solid(Color.HotPink), new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY)); + })); + + image.Mutate(ctx => ctx.ProcessWithCanvas(blendOptions, canvas => + { + canvas.Fill(Brushes.Solid(transparentRed), new EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)); + })); + + VerifyImage(provider, blending, composition, image); + } + + [Theory] + [WithBlankImage(nameof(BlendingsModes), 250, 250, PixelTypes.Rgba32)] + public void BlendingsDarkBlueRectBlendBlackEllipse( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) + where TPixel : unmanaged, IPixel + { + using Image destinationImage = provider.GetImage(); + using Image sourceImage = provider.GetImage(); + + int scaleX = destinationImage.Width / 100; + int scaleY = destinationImage.Height / 100; + + DrawingOptions blendOptions = CreateBlendOptions(blending, composition); + + destinationImage.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.DarkBlue), new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)); + })); + + sourceImage.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.Black), new EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)); + })); + + destinationImage.Mutate(ctx => ctx.ProcessWithCanvas(blendOptions, canvas => + { + canvas.DrawImage( + sourceImage, + sourceImage.Bounds, + new RectangleF(0, 0, destinationImage.Width, destinationImage.Height)); + })); + + VerifyImage(provider, blending, composition, destinationImage); + } + + private static DrawingOptions CreateBlendOptions( + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition) => + new() + { + GraphicsOptions = new GraphicsOptions + { + Antialias = true, + ColorBlendingMode = blending, + AlphaCompositionMode = composition + } + }; + + private static void VerifyImage( + TestImageProvider provider, + PixelColorBlendingMode blending, + PixelAlphaCompositionMode composition, + Image image) + where TPixel : unmanaged, IPixel + { + image.DebugSave( + provider, + new { composition, blending }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + ImageComparer comparer = ImageComparer.TolerantPercentage(0.01F, 3); + image.CompareFirstFrameToReferenceOutput( + comparer, + provider, + new { composition, blending }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Clear.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Clear.cs new file mode 100644 index 000000000..c837a76c9 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Clear.cs @@ -0,0 +1,157 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + [Theory] + [WithBlankImage(1, 1, PixelTypes.Rgba32)] + [WithBlankImage(7, 4, PixelTypes.Rgba32)] + [WithBlankImage(16, 7, PixelTypes.Rgba32)] + [WithBlankImage(33, 32, PixelTypes.Rgba32)] + [WithBlankImage(400, 500, PixelTypes.Rgba32)] + public void Clear_DoesNotDependOnSize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color color = Color.HotPink; + DrawingOptions options = new(); + + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Clear(Brushes.Solid(color)))); + + image.DebugSave(provider, appendPixelTypeToFileName: false); + image.ComparePixelBufferTo(color); + } + + [Theory] + [WithBlankImage(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] + public void Clear_DoesNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color color = Color.HotPink; + DrawingOptions options = new(); + + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Clear(Brushes.Solid(color)))); + + image.DebugSave(provider, appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] + [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] + public void Clear_WhenColorIsOpaque_OverridePreviousColor( + TestImageProvider provider, + string newColorName) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color color = TestUtils.GetColorByName(newColorName); + DrawingOptions options = new(); + + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Clear(Brushes.Solid(color)))); + + image.DebugSave( + provider, + newColorName, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] + [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] + public void Clear_AlwaysOverridesPreviousColor( + TestImageProvider provider, + string newColorName) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color color = TestUtils.GetColorByName(newColorName).WithAlpha(0.5F); + DrawingOptions options = new(); + + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Clear(Brushes.Solid(color)))); + + image.DebugSave( + provider, + newColorName, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] + public void Clear_Region(TestImageProvider provider, int x0, int y0, int w, int h) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color clearColor = Color.Blue; + Color backgroundColor = Color.Red; + Rectangle region = new(x0, y0, w, h); + DrawingOptions options = new(); + + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Clear(Brushes.Solid(clearColor), region))); + + image.DebugSave(provider, $"(x{x0},y{y0},w{w},h{h})", appendPixelTypeToFileName: false); + AssertRegionFill(image, region, clearColor, backgroundColor); + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] + public void Clear_Region_WorksOnWrappedMemoryImage( + TestImageProvider provider, + int x0, + int y0, + int w, + int h) + where TPixel : unmanaged, IPixel + { + using Image source = provider.GetImage(); + Assert.True(source.DangerousTryGetSinglePixelMemory(out Memory sourcePixels)); + TestMemoryManager memoryManager = TestMemoryManager.CreateAsCopyOf(sourcePixels.Span); + using Image wrapped = Image.WrapMemory(memoryManager.Memory, source.Width, source.Height); + + Color clearColor = Color.Blue; + Color backgroundColor = Color.Red; + Rectangle region = new(x0, y0, w, h); + DrawingOptions options = new(); + + wrapped.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Clear(Brushes.Solid(clearColor), region))); + + wrapped.DebugSave(provider, $"(x{x0},y{y0},w{w},h{h})", appendPixelTypeToFileName: false); + AssertRegionFill(wrapped, region, clearColor, backgroundColor); + } + + private static void AssertRegionFill( + Image image, + Rectangle region, + Color inside, + Color outside) + where TPixel : unmanaged, IPixel + { + TPixel insidePixel = inside.ToPixel(); + TPixel outsidePixel = outside.ToPixel(); + Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; + + for (int y = 0; y < image.Height; y++) + { + Span row = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < image.Width; x++) + { + TPixel expected = region.Contains(x, y) ? insidePixel : outsidePixel; + Assert.Equal(expected, row[x]); + } + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Clip.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Clip.cs new file mode 100644 index 000000000..2f680d51e --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Clip.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Linq; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 0, 0, 0.5)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, -20, -20, 0.5)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, -20, -100, 0.5)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 20, 20, 0.5)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 40, 60, 0.2)] + public void ClipOffset(TestImageProvider provider, float dx, float dy, float sizeMult) + where TPixel : unmanaged, IPixel + { + FormattableString testDetails = $"offset_x{dx}_y{dy}"; + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(canvas => + { + Rectangle bounds = canvas.Bounds; + int outerRadii = (int)(Math.Min(bounds.Width, bounds.Height) * sizeMult); + Star star = new(new PointF(bounds.Width / 2F, bounds.Height / 2F), 5, outerRadii / 2F, outerRadii); + Matrix4x4 builder = Matrix4x4.CreateTranslation(dx, dy, 0); + canvas.Process(star.Transform(builder), ctx => ctx.DetectEdges()); + }), + testOutputDetails: testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] + public void ClipConstrainsOperationToClipBounds(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(canvas => + { + Rectangle bounds = canvas.Bounds; + RectangleF rect = new(0, 0, bounds.Width / 2F, bounds.Height / 2F); + RectangularPolygon clipRect = new(rect); + canvas.Process(clipRect, ctx => ctx.Flip(FlipMode.Vertical)); + }), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + [Fact] + public void ClipIssue250VerticalHorizontalCountShouldMatch() + { + PathCollection clip = new(new RectangularPolygon(new PointF(24, 16), new PointF(777, 385))); + + Path vertical = new(new LinearLineSegment(new PointF(26, 384), new PointF(26, 163))); + Path horizontal = new(new LinearLineSegment(new PointF(26, 163), new PointF(176, 163))); + + IPath reverse = vertical.Clip(clip); + int verticalCount = vertical.Clip(reverse).Flatten().Select(x => x.Points).Count(); + + reverse = horizontal.Clip(clip); + int horizontalCount = horizontal.Clip(reverse).Flatten().Select(x => x.Points).Count(); + + Assert.Equal(verticalCount, horizontalCount); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillOutsideBounds.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillOutsideBounds.cs new file mode 100644 index 000000000..6cb0e4424 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillOutsideBounds.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + public static TheoryData FillOutsideBoundsCircleCoordinates { get; } = new() + { + { -110, -60 }, { 0, -60 }, { 110, -60 }, + { -110, -50 }, { 0, -50 }, { 110, -50 }, + { -110, -49 }, { 0, -49 }, { 110, -49 }, + { -110, -20 }, { 0, -20 }, { 110, -20 }, + { -110, -50 }, { 0, -60 }, { 110, -60 }, + { -110, 0 }, { -99, 0 }, { 0, 0 }, { 99, 0 }, { 110, 0 }, + }; + + [Theory] + [InlineData(-100)] + [InlineData(-99)] + [InlineData(99)] + [InlineData(100)] + public void FillOutsideBoundsDrawRectactangleOutsideBoundsDrawingArea(int xpos) + { + int width = 100; + int height = 100; + + using Image image = new(width, height, Color.Red.ToPixel()); + + Rectangle rectangle = new(xpos, 0, width, height); + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(Color.Black), rectangle))); + } + + [Theory] + [WithSolidFilledImages(nameof(FillOutsideBoundsCircleCoordinates), 100, 100, nameof(Color.Red), PixelTypes.Rgba32)] + public void FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea(TestImageProvider provider, int xpos, int ypos) + { + int width = 100; + int height = 100; + + EllipsePolygon circle = new(xpos, ypos, width, height); + + provider.RunValidatingProcessorTest( + ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(Color.Black), circle)), + $"({xpos}_{ypos})", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillPath.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillPath.cs new file mode 100644 index 000000000..d9e92df9c --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillPath.cs @@ -0,0 +1,151 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths + [Theory] + [WithSolidFilledImages(325, 325, "White", PixelTypes.Rgba32)] + public void FillPathSVGArcs(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + PathBuilder pb = new(); + + pb.MoveTo(new Vector2(80, 80)) + .ArcTo(45, 45, 0, false, false, new Vector2(125, 125)) + .LineTo(new Vector2(125, 80)) + .CloseFigure(); + + IPath path = pb.Build(); + + pb = new PathBuilder(); + pb.MoveTo(new Vector2(230, 80)) + .ArcTo(45, 45, 0, true, false, new Vector2(275, 125)) + .LineTo(new Vector2(275, 80)) + .CloseFigure(); + + IPath path2 = pb.Build(); + + pb = new PathBuilder(); + pb.MoveTo(new Vector2(80, 230)) + .ArcTo(45, 45, 0, false, true, new Vector2(125, 275)) + .LineTo(new Vector2(125, 230)) + .CloseFigure(); + + IPath path3 = pb.Build(); + + pb = new PathBuilder(); + pb.MoveTo(new Vector2(230, 230)) + .ArcTo(45, 45, 0, true, true, new Vector2(275, 275)) + .LineTo(new Vector2(275, 230)) + .CloseFigure(); + + IPath path4 = pb.Build(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.Green), path); + canvas.Fill(Brushes.Solid(Color.Red), path2); + canvas.Fill(Brushes.Solid(Color.Purple), path3); + canvas.Fill(Brushes.Solid(Color.Blue), path4); + })); + + image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); + } + + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc + [Theory] + [WithSolidFilledImages(150, 200, "White", PixelTypes.Rgba32)] + public void FillPathCanvasArcs(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + for (int i = 0; i <= 3; i++) + { + for (int j = 0; j <= 2; j++) + { + PathBuilder pb = new(); + + float x = 25 + (j * 50); // x coordinate + float y = 25 + (i * 50); // y coordinate + float radius = 20; // Arc radius + float startAngle = 0; // Starting point on circle + float endAngle = 180F + (180F * j / 2F); // End point on circle + bool counterclockwise = i % 2 == 1; // Draw counterclockwise + + // To move counterclockwise we offset our sweepAngle parameter + // Canvas likely does something similar. + if (counterclockwise) + { + // 360 becomes zero and we don't accept that as a parameter (won't render). + if (endAngle < 360F) + { + endAngle = (360F - endAngle) % 360F; + } + + endAngle *= -1; + } + + pb.AddArc(x, y, radius, radius, 0, startAngle, endAngle); + + if (i > 1) + { + canvas.Fill(Brushes.Solid(Color.Black), pb.Build()); + } + else + { + canvas.Draw(Pens.Solid(Color.Black, 1F), pb.Build()); + } + } + } + })); + + image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(5e-3f), + provider, + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithSolidFilledImages(400, 250, "White", PixelTypes.Rgba32)] + public void FillPathArcToAlternates(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Test alternate syntax. Both should overlap creating an orange arc. + PathBuilder pb = new(); + + pb.MoveTo(new Vector2(50, 50)); + pb.ArcTo(20, 50, -72, false, true, new Vector2(200, 200)); + IPath path = pb.Build(); + + pb = new PathBuilder(); + pb.MoveTo(new Vector2(50, 50)); + pb.AddSegment(new ArcLineSegment(new Vector2(50, 50), new Vector2(200, 200), new SizeF(20, 50), -72F, true, true)); + IPath path2 = pb.Build(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.Yellow), path); + canvas.Fill(Brushes.Solid(Color.Red.WithAlpha(.5F)), path2); + })); + + image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillSolidBrush.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillSolidBrush.cs new file mode 100644 index 000000000..1d695bffc --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.FillSolidBrush.cs @@ -0,0 +1,192 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + public static readonly TheoryData FillSolidBrush_BlendData = + new() + { + { false, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, + { false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, + { false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, + { false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, + { false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, + { false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, + { false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, + { false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, + { false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, + { false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, + { false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, + { false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, + { true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, + { true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, + { true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, + { true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, + { true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, + { true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, + { true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, + { true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, + { true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, + { true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, + { true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, + { true, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, + }; + + [Theory] + [WithBlankImage(1, 1, PixelTypes.Rgba32)] + [WithBlankImage(7, 4, PixelTypes.Rgba32)] + [WithBlankImage(16, 7, PixelTypes.Rgba32)] + [WithBlankImage(33, 32, PixelTypes.Rgba32)] + [WithBlankImage(400, 500, PixelTypes.Rgba32)] + public void FillSolidBrush_DoesNotDependOnSize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color color = Color.HotPink; + DrawingOptions options = new(); + + image.Mutate(c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(color)))); + + image.DebugSave(provider, appendPixelTypeToFileName: false); + image.ComparePixelBufferTo(color); + } + + [Theory] + [WithBlankImage(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] + public void FillSolidBrush_DoesNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color color = Color.HotPink; + DrawingOptions options = new(); + + image.Mutate(c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(color)))); + + image.DebugSave(provider, appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] + [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] + public void FillSolidBrush_WhenColorIsOpaque_OverridePreviousColor( + TestImageProvider provider, + string newColorName) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color color = TestUtils.GetColorByName(newColorName); + DrawingOptions options = new(); + + image.Mutate(c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(color)))); + + image.DebugSave( + provider, + newColorName, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] + public void FillSolidBrush_Region(TestImageProvider provider, int x0, int y0, int w, int h) + where TPixel : unmanaged, IPixel + { + FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; + Rectangle region = new(x0, y0, w, h); + Color color = Color.Blue; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(color), region)), + testDetails, + ImageComparer.Exact); + } + + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] + public void FillSolidBrush_Region_WorksOnWrappedMemoryImage( + TestImageProvider provider, + int x0, + int y0, + int w, + int h) + where TPixel : unmanaged, IPixel + { + FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; + Rectangle region = new(x0, y0, w, h); + Color color = Color.Blue; + + provider.RunValidatingProcessorTestOnWrappedMemoryImage( + c => c.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(color), region)), + testDetails, + ImageComparer.Exact, + useReferenceOutputFrom: nameof(this.FillSolidBrush_Region)); + } + + [Theory] + [WithSolidFilledImages(nameof(FillSolidBrush_BlendData), 16, 16, "Red", PixelTypes.Rgba32)] + public void FillSolidBrush_BlendFillColorOverBackground( + TestImageProvider provider, + bool triggerFillRegion, + string newColorName, + float alpha, + PixelColorBlendingMode blenderMode, + float blendPercentage) + where TPixel : unmanaged, IPixel + { + Color fillColor = TestUtils.GetColorByName(newColorName).WithAlpha(alpha); + + using Image image = provider.GetImage(); + TPixel bgColor = image[0, 0]; + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions + { + Antialias = false, + ColorBlendingMode = blenderMode, + BlendPercentage = blendPercentage + } + }; + + if (triggerFillRegion) + { + RectangularPolygon path = new(0, 0, 16, 16); + image.Mutate(c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(fillColor), path))); + } + else + { + image.Mutate(c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(fillColor)))); + } + + var testOutputDetails = new + { + triggerFillRegion, + newColorName, + alpha, + blenderMode, + blendPercentage + }; + + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender( + blenderMode, + PixelAlphaCompositionMode.SrcOver); + TPixel expectedPixel = blender.Blend(bgColor, fillColor.ToPixel(), blendPercentage); + image.ComparePixelBufferTo(expectedPixel); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.GradientBrushes.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.GradientBrushes.cs new file mode 100644 index 000000000..c69466242 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.GradientBrushes.cs @@ -0,0 +1,661 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Globalization; +using System.Text; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + private static readonly ImageComparer EllipticGradientTolerantComparer = ImageComparer.TolerantPercentage(0.01F); + private static readonly ImageComparer LinearGradientTolerantComparer = ImageComparer.TolerantPercentage(0.01F); + private static readonly ImageComparer RadialGradientTolerantComparer = ImageComparer.TolerantPercentage(0.01F); + private static readonly ImageComparer SweepGradientTolerantComparer = ImageComparer.TolerantPercentage(0.01F); + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0F, 360F)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 90F, 450F)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 180F, 540F)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 270F, 630F)] + public void FillSweepGradientBrush_RendersFullSweep_Every90Degrees( + TestImageProvider provider, + float start, + float end) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + SweepGradientTolerantComparer, + image => + { + SweepGradientBrush brush = new( + new Point(100, 100), + start, + end, + GradientRepetitionMode.None, + new ColorStop(0, Color.Red), + new ColorStop(0.25F, Color.Yellow), + new ColorStop(0.5F, Color.Green), + new ColorStop(0.75F, Color.Blue), + new ColorStop(1, Color.Red)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + $"start({start},end{end})", + false, + false); + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32)] + public void FillRadialGradientBrushWithEqualColorsReturnsUnicolorImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color red = Color.Red; + + RadialGradientBrush brush = + new( + new Point(0, 0), + 100, + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + // No reference image needed: the whole output should be a single color. + image.ComparePixelBufferTo(red); + } + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 100, 100)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0, 0)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 100, 0)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0, 100)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, -40, 100)] + public void FillRadialGradientBrushWithDifferentCentersReturnsImage( + TestImageProvider provider, + int centerX, + int centerY) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + RadialGradientTolerantComparer, + image => + { + RadialGradientBrush brush = new( + new Point(centerX, centerY), + image.Width / 2F, + GradientRepetitionMode.None, + new ColorStop(0, Color.Red), + new ColorStop(1, Color.Yellow)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + $"center({centerX},{centerY})", + false, + false); + + [Theory] + [WithBlankImage(10, 10, PixelTypes.Rgba32)] + public void FillEllipticGradientBrushWithEqualColorsReturnsUnicolorImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color red = Color.Red; + + using Image image = provider.GetImage(); + + EllipticGradientBrush unicolorEllipticGradientBrush = + new( + new Point(0, 0), + new Point(10, 0), + 1.0F, + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); + + DrawingOptions options = new(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Fill(unicolorEllipticGradientBrush))); + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + // No reference image needed: the whole output should be a single color. + image.ComparePixelBufferTo(red); + } + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.2)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.6)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 2.0)] + public void FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio(TestImageProvider provider, float ratio) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + Color yellow = Color.Yellow; + Color red = Color.Red; + Color black = Color.Black; + + EllipticGradientBrush brush = new( + new Point(image.Width / 2, image.Height / 2), + new Point(image.Width / 2, image.Width * 2 / 3), + ratio, + GradientRepetitionMode.None, + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); + + FormattableString outputDetails = $"{ratio:F2}"; + DrawingOptions options = new(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Fill(brush))); + image.DebugSave(provider, outputDetails, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + EllipticGradientTolerantComparer, + provider, + outputDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1, 0)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4, 0)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8, 0)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0, 0)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1, 45)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4, 45)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8, 45)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0, 45)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1, 90)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4, 90)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8, 90)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0, 90)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.1, 30)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.4, 30)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0.8, 30)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 1.0, 30)] + public void FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio( + TestImageProvider provider, + float ratio, + float rotationInDegree) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + Color yellow = Color.Yellow; + Color red = Color.Red; + Color black = Color.Black; + + Point center = new(image.Width / 2, image.Height / 2); + + double rotation = Math.PI * rotationInDegree / 180.0; + double cos = Math.Cos(rotation); + double sin = Math.Sin(rotation); + + int offsetY = image.Height / 6; + int axisX = center.X + (int)-(offsetY * sin); + int axisY = center.Y + (int)(offsetY * cos); + + EllipticGradientBrush brush = new( + center, + new Point(axisX, axisY), + ratio, + GradientRepetitionMode.None, + new ColorStop(0, yellow), + new ColorStop(1, red), + new ColorStop(1, black)); + + FormattableString outputDetails = $"{ratio:F2}_AT_{rotationInDegree:00}deg"; + DrawingOptions options = new(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Fill(brush))); + image.DebugSave(provider, outputDetails, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + EllipticGradientTolerantComparer, + provider, + outputDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + public enum FillLinearGradientBrushImageCorner + { + TopLeft = 0, + TopRight = 1, + BottomLeft = 2, + BottomRight = 3 + } + + [Theory] + [WithBlankImage(10, 10, PixelTypes.Rgba32)] + public void FillLinearGradientBrushWithEqualColorsReturnsUnicolorImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Color red = Color.Red; + + LinearGradientBrush brush = new( + new Point(0, 0), + new Point(10, 0), + GradientRepetitionMode.None, + new ColorStop(0, red), + new ColorStop(1, red)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + // No reference image needed: the whole output should be a single color. + image.ComparePixelBufferTo(red); + } + + [Theory] + [WithBlankImage(20, 10, PixelTypes.Rgba32)] + [WithBlankImage(20, 10, PixelTypes.Argb32)] + [WithBlankImage(20, 10, PixelTypes.Rgb24)] + public void FillLinearGradientBrushDoesNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + LinearGradientTolerantComparer, + image => + { + LinearGradientBrush brush = new( + new Point(0, 0), + new Point(image.Width, 0), + GradientRepetitionMode.None, + new ColorStop(0, Color.Blue), + new ColorStop(1, Color.Yellow)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + appendSourceFileOrDescription: false); + + [Theory] + [WithBlankImage(500, 10, PixelTypes.Rgba32)] + public void FillLinearGradientBrushHorizontalReturnsUnicolorColumns(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + LinearGradientTolerantComparer, + image => + { + LinearGradientBrush brush = new( + new Point(0, 0), + new Point(image.Width, 0), + GradientRepetitionMode.None, + new ColorStop(0, Color.Red), + new ColorStop(1, Color.Yellow)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + false, + false); + + [Theory] + [WithBlankImage(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.DontFill)] + [WithBlankImage(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.None)] + [WithBlankImage(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Repeat)] + [WithBlankImage(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Reflect)] + public void FillLinearGradientBrushHorizontalGradientWithRepMode( + TestImageProvider provider, + GradientRepetitionMode repetitionMode) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + LinearGradientTolerantComparer, + image => + { + LinearGradientBrush brush = new( + new Point(0, 0), + new Point(image.Width / 10, 0), + repetitionMode, + new ColorStop(0, Color.Red), + new ColorStop(1, Color.Yellow)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + $"{repetitionMode}", + false, + false); + + [Theory] + [WithBlankImage(200, 100, PixelTypes.Rgba32, new[] { 0.5f })] + [WithBlankImage(200, 100, PixelTypes.Rgba32, new[] { 0.2f, 0.4f, 0.6f, 0.8f })] + [WithBlankImage(200, 100, PixelTypes.Rgba32, new[] { 0.1f, 0.3f, 0.6f })] + public void FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns( + TestImageProvider provider, + float[] pattern) + where TPixel : unmanaged, IPixel + { + string variant = string.Join("_", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture))); + + Assert.True(pattern.Length > 0); + + Color black = Color.Black; + Color white = Color.White; + + ColorStop[] colorStops = + Enumerable.Repeat(new ColorStop(0, black), 1) + .Concat( + pattern.SelectMany( + (f, index) => + new[] + { + new ColorStop(f, index % 2 == 0 ? black : white), + new ColorStop(f, index % 2 == 0 ? white : black) + })) + .Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? black : white), 1)) + .ToArray(); + + using Image image = provider.GetImage(); + + LinearGradientBrush brush = + new( + new Point(0, 0), + new Point(image.Width, 0), + GradientRepetitionMode.None, + colorStops); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + + image.DebugSave( + provider, + variant, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + Assert.All( + Enumerable.Range(0, image.Width).Select(i => image[i, 0]), + color => Assert.True( + color.Equals(black.ToPixel()) || color.Equals(white.ToPixel()))); + + image.CompareToReferenceOutput( + LinearGradientTolerantComparer, + provider, + variant, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(10, 500, PixelTypes.Rgba32)] + public void FillLinearGradientBrushVerticalBrushReturnsUnicolorRows( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.VerifyOperation( + image => + { + LinearGradientBrush brush = new( + new Point(0, 0), + new Point(0, image.Height), + GradientRepetitionMode.None, + new ColorStop(0, Color.Red), + new ColorStop(1, Color.Yellow)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + + VerifyAllRowsAreUnicolor(image); + }, + false, + false); + + static void VerifyAllRowsAreUnicolor(Image image) + { + for (int y = 0; y < image.Height; y++) + { + Span row = image.GetRootFramePixelBuffer().DangerousGetRowSpan(y); + TPixel firstColorOfRow = row[0]; + foreach (TPixel p in row) + { + Assert.Equal(firstColorOfRow, p); + } + } + } + } + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32, FillLinearGradientBrushImageCorner.TopLeft)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, FillLinearGradientBrushImageCorner.TopRight)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, FillLinearGradientBrushImageCorner.BottomLeft)] + [WithBlankImage(200, 200, PixelTypes.Rgba32, FillLinearGradientBrushImageCorner.BottomRight)] + public void FillLinearGradientBrushDiagonalReturnsCorrectImages( + TestImageProvider provider, + FillLinearGradientBrushImageCorner startCorner) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + Assert.True( + image.Height == image.Width, + "For the math check block at the end the image must be squared, but it is not."); + + int startX = (int)startCorner % 2 == 0 ? 0 : image.Width - 1; + int startY = startCorner > FillLinearGradientBrushImageCorner.TopRight ? 0 : image.Height - 1; + int endX = image.Height - startX - 1; + int endY = image.Width - startY - 1; + + LinearGradientBrush brush = + new( + new Point(startX, startY), + new Point(endX, endY), + GradientRepetitionMode.None, + new ColorStop(0, Color.Red), + new ColorStop(1, Color.Yellow)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.DebugSave( + provider, + startCorner, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + int verticalSign = startY == 0 ? 1 : -1; + int horizontalSign = startX == 0 ? 1 : -1; + + for (int i = 0; i < image.Height; i++) + { + TPixel colorOnDiagonal = image[i, i]; + int orthoCount = 0; + for (int offset = -orthoCount; offset < orthoCount; offset++) + { + Assert.Equal(colorOnDiagonal, image[i + (horizontalSign * offset), i + (verticalSign * offset)]); + } + } + + image.CompareToReferenceOutput( + LinearGradientTolerantComparer, + provider, + startCorner, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] + [WithBlankImage(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] + [WithBlankImage(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f }, new[] { 0, 1, 2, 0 })] + [WithBlankImage(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f }, new[] { 0, 1, 3 })] + public void FillLinearGradientBrushArbitraryGradients( + TestImageProvider provider, + int startX, + int startY, + int endX, + int endY, + float[] stopPositions, + int[] stopColorCodes) + where TPixel : unmanaged, IPixel + { + Color[] colors = + [ + Color.Navy, Color.LightGreen, Color.Yellow, + Color.Red + ]; + + StringBuilder coloringVariant = new(); + ColorStop[] colorStops = new ColorStop[stopPositions.Length]; + + for (int i = 0; i < stopPositions.Length; i++) + { + Color color = colors[stopColorCodes[i % colors.Length]]; + float position = stopPositions[i]; + colorStops[i] = new ColorStop(position, color); + coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color.ToPixel().ToHex(), position); + } + + FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; + + provider.VerifyOperation( + image => + { + LinearGradientBrush brush = new( + new Point(startX, startY), + new Point(endX, endY), + GradientRepetitionMode.None, + colorStops); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + variant, + false, + false); + } + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32, 0, 0, 199, 199, new[] { 0f, .25f, .5f, .75f, 1f }, new[] { 0, 1, 2, 3, 4 })] + public void FillLinearGradientBrushMultiplePointGradients( + TestImageProvider provider, + int startX, + int startY, + int endX, + int endY, + float[] stopPositions, + int[] stopColorCodes) + where TPixel : unmanaged, IPixel + { + Color[] colors = + [ + Color.Black, Color.Blue, Color.Red, + Color.White, Color.Lime + ]; + + StringBuilder coloringVariant = new(); + ColorStop[] colorStops = new ColorStop[stopPositions.Length]; + + for (int i = 0; i < stopPositions.Length; i++) + { + Color color = colors[stopColorCodes[i % colors.Length]]; + float position = stopPositions[i]; + colorStops[i] = new ColorStop(position, color); + coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color.ToPixel().ToHex(), position); + } + + FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; + + provider.VerifyOperation( + image => + { + LinearGradientBrush brush = new( + new Point(startX, startY), + new Point(endX, endY), + GradientRepetitionMode.None, + colorStops); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + variant, + false, + false); + } + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32)] + public void FillLinearGradientBrushGradientsWithTransparencyOnExistingBackground(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.VerifyOperation( + image => + { + int width = image.Width; + int height = image.Height; + + image.Mutate(ctx => + { + ctx.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(Color.Red))); + + DrawingOptions glossOptions = new() + { + GraphicsOptions = new GraphicsOptions + { + Antialias = true, + ColorBlendingMode = PixelColorBlendingMode.Normal, + AlphaCompositionMode = PixelAlphaCompositionMode.SrcAtop + } + }; + + IPathCollection glossPath = BuildGloss(width, height); + LinearGradientBrush linearGradientBrush = new( + new Point(0, 0), + new Point(0, height / 2), + GradientRepetitionMode.Repeat, + new ColorStop(0, Color.White.WithAlpha(0.5f)), + new ColorStop(1, Color.White.WithAlpha(0.25f))); + + ctx.ProcessWithCanvas(glossOptions, canvas => canvas.Fill(linearGradientBrush, glossPath)); + }); + }); + + static IPathCollection BuildGloss(int imageWidth, int imageHeight) + { + PathBuilder pathBuilder = new(); + pathBuilder.AddLine(new PointF(0, 0), new PointF(imageWidth, 0)); + pathBuilder.AddLine(new PointF(imageWidth, 0), new PointF(imageWidth, imageHeight * 0.4f)); + pathBuilder.AddQuadraticBezier( + new PointF(imageWidth, imageHeight * 0.4f), + new PointF(imageWidth / 2f, imageHeight * 0.6f), + new PointF(0, imageHeight * 0.4f)); + pathBuilder.CloseFigure(); + return new PathCollection(pathBuilder.Build()); + } + } + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgb24)] + public void FillLinearGradientBrushBrushApplicatorIsThreadSafeIssue1044(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + LinearGradientTolerantComparer, + image => + { + PathGradientBrush brush = new( + [new PointF(0, 0), new PointF(200, 0), new PointF(200, 200), new PointF(0, 200), new PointF(0, 0)], + [Color.Red, Color.Yellow, Color.Green, Color.DarkCyan, Color.Red]); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + false, + false); + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32)] + public void FillLinearGradientBrushRotatedGradient(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + image => + { + LinearGradientBrush brush = new( + new Point(0, 0), + new Point(200, 200), + new Point(0, 100), + GradientRepetitionMode.None, + new ColorStop(0, Color.Red), + new ColorStop(1, Color.Yellow)); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + false, + false); +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.ImageBrushes.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.ImageBrushes.cs new file mode 100644 index 000000000..d1a4253e4 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.ImageBrushes.cs @@ -0,0 +1,211 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + [Fact] + public void FillImageBrushDoesNotDisposeImage() + { + using (Image source = new(5, 5)) + { + ImageBrush brush = new(source); + using (Image destination = new(10, 10)) + { + destination.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush, new Rectangle(0, 0, 10, 10)))); + destination.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush, new Rectangle(0, 0, 10, 10)))); + } + } + } + + [Theory] + [WithTestPatternImage(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void FillImageBrushUseBrushOfDifferentPixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; + using Image background = provider.GetImage(); + using Image overlay = provider.PixelType == PixelTypes.Rgba32 + ? Image.Load(data) + : Image.Load(data); + + ImageBrush brush = new(overlay); + background.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + + background.DebugSave(provider, appendSourceFileOrDescription: false); + background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithTestPatternImage(200, 200, PixelTypes.Rgba32)] + public void FillImageBrushCanDrawLandscapeImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; + using Image background = provider.GetImage(); + using Image overlay = Image.Load(data); + + overlay.Mutate(ctx => ctx.Crop(new Rectangle(0, 0, 125, 90))); + + ImageBrush brush = new(overlay); + background.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + + background.DebugSave(provider, appendSourceFileOrDescription: false); + background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithTestPatternImage(200, 200, PixelTypes.Rgba32)] + public void FillImageBrushCanDrawPortraitImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; + using Image background = provider.GetImage(); + using Image overlay = Image.Load(data); + + overlay.Mutate(ctx => ctx.Crop(new Rectangle(0, 0, 90, 125))); + + ImageBrush brush = new(overlay); + background.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + + background.DebugSave(provider, appendSourceFileOrDescription: false); + background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithTestPatternImage(400, 400, PixelTypes.Rgba32)] + public void FillImageBrushCanOffsetImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; + using Image background = provider.GetImage(); + using Image overlay = Image.Load(data); + + ImageBrush brush = new(overlay); + background.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(brush, new Rectangle(0, 0, 400, 200)); + canvas.Fill(brush, new Rectangle(-100, 200, 500, 200)); + })); + + background.DebugSave(provider, appendSourceFileOrDescription: false); + background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithTestPatternImage(400, 400, PixelTypes.Rgba32)] + public void FillImageBrushCanOffsetViaBrushImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; + using Image background = provider.GetImage(); + using Image overlay = Image.Load(data); + + ImageBrush brush = new(overlay); + ImageBrush brushOffset = new(overlay, new Point(100, 0)); + + background.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(brush, new Rectangle(0, 0, 400, 200)); + canvas.Fill(brushOffset, new Rectangle(0, 200, 400, 200)); + })); + + background.DebugSave(provider, appendSourceFileOrDescription: false); + background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(1000, 1000, "White", PixelTypes.Rgba32)] + public void FillImageBrushCanDrawOffsetImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; + using Image background = provider.GetImage(); + + using Image templateImage = Image.Load(data); + using Image finalTexture = BuildMultiRowTexture(templateImage); + + finalTexture.Mutate(ctx => ctx.Resize(100, 200)); + + ImageBrush brush = new(finalTexture); + background.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + + background.DebugSave(provider, appendSourceFileOrDescription: false); + background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + + Image BuildMultiRowTexture(Image sourceTexture) + { + int halfWidth = sourceTexture.Width / 2; + + Image final = sourceTexture.Clone(ctx => ctx.Resize(new ResizeOptions + { + Size = new Size(templateImage.Width, templateImage.Height * 2), + Position = AnchorPositionMode.TopLeft, + Mode = ResizeMode.Pad, + }) + .DrawImage(templateImage, new Point(halfWidth, sourceTexture.Height), new Rectangle(0, 0, halfWidth, sourceTexture.Height), 1) + .DrawImage(templateImage, new Point(0, templateImage.Height), new Rectangle(halfWidth, 0, halfWidth, sourceTexture.Height), 1)); + return final; + } + } + + [Theory] + [WithSolidFilledImages(1000, 1000, "White", PixelTypes.Rgba32)] + public void FillImageBrushCanDrawNegativeOffsetImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; + using Image background = provider.GetImage(); + using Image overlay = Image.Load(data); + + overlay.Mutate(ctx => ctx.Resize(100, 100)); + + ImageBrush halfBrush = new(overlay, new RectangleF(50, 0, 50, 100)); + ImageBrush fullBrush = new(overlay); + + background.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + FillImageBrushDrawFull(canvas, new Size(100, 100), fullBrush, halfBrush, background.Width, background.Height))); + + background.DebugSave(provider, appendSourceFileOrDescription: false); + background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); + } + + private static void FillImageBrushDrawFull( + IDrawingCanvas canvas, + Size size, + ImageBrush brush, + ImageBrush halfBrush, + int width, + int height) + { + int y = 0; + while (y < height) + { + bool half = (y / size.Height) % 2 != 0; + int x = 0; + while (x < width) + { + if (half) + { + int halfWidth = size.Width / 2; + canvas.Fill(halfBrush, new Rectangle(x, y, halfWidth, size.Height)); + x += halfWidth; + half = false; + } + else + { + canvas.Fill(brush, new Rectangle(x, y, size.Width, size.Height)); + x += size.Width; + } + } + + y += size.Height; + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.PathGradientBrushes.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.PathGradientBrushes.cs new file mode 100644 index 000000000..f51dee4a3 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.PathGradientBrushes.cs @@ -0,0 +1,201 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + private static readonly ImageComparer PathGradientTolerantComparer = ImageComparer.TolerantPercentage(0.01f); + + [Theory] + [WithBlankImage(10, 10, PixelTypes.Rgba32)] + public void FillPathGradientBrushFillRectangleWithDifferentColors(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + PathGradientTolerantComparer, + image => + { + PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; + Color[] colors = [Color.Black, Color.Red, Color.Yellow, Color.Green]; + + PathGradientBrush brush = new(points, colors); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + }); + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPathGradientBrushFillTriangleWithDifferentColors(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + PathGradientTolerantComparer, + image => + { + PointF[] points = [new(10, 0), new(20, 20), new(0, 20)]; + Color[] colors = [Color.Red, Color.Green, Color.Blue]; + + PathGradientBrush brush = new(points, colors); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + }); + + [Theory] + [WithBlankImage(20, 20, PixelTypes.HalfSingle)] + public void FillPathGradientBrushFillTriangleWithGreyscale(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + ImageComparer.TolerantPercentage(0.02f), + image => + { + PointF[] points = [new(10, 0), new(20, 20), new(0, 20)]; + + Color c1 = Color.FromPixel(new HalfSingle(-1)); + Color c2 = Color.FromPixel(new HalfSingle(0)); + Color c3 = Color.FromPixel(new HalfSingle(1)); + + Color[] colors = [c1, c2, c3]; + + PathGradientBrush brush = new(points, colors); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + }); + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPathGradientBrushFillTriangleWithDifferentColorsCenter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + PathGradientTolerantComparer, + image => + { + PointF[] points = [new(10, 0), new(20, 20), new(0, 20)]; + Color[] colors = [Color.Red, Color.Green, Color.Blue]; + + PathGradientBrush brush = new(points, colors, Color.White); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + }); + + [Theory] + [WithBlankImage(10, 10, PixelTypes.Rgba32)] + public void FillPathGradientBrushFillRectangleWithSingleColor(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; + Color[] colors = [Color.Red]; + + PathGradientBrush brush = new(points, colors); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.ComparePixelBufferTo(Color.Red); + } + + [Theory] + [WithBlankImage(10, 10, PixelTypes.Rgba32)] + public void FillPathGradientBrushShouldRotateTheColorsWhenThereAreMorePoints(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + PathGradientTolerantComparer, + image => + { + PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; + Color[] colors = [Color.Red, Color.Yellow]; + + PathGradientBrush brush = new(points, colors); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + }); + + [Theory] + [WithBlankImage(10, 10, PixelTypes.Rgba32)] + public void FillPathGradientBrushFillWithCustomCenterColor(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + PathGradientTolerantComparer, + image => + { + PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; + Color[] colors = [Color.Black, Color.Red, Color.Yellow, Color.Green]; + + PathGradientBrush brush = new(points, colors, Color.White); + + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + }); + + [Fact] + public void FillPathGradientBrushShouldThrowArgumentNullExceptionWhenLinesAreNull() + { + Color[] colors = [Color.Black, Color.Red, Color.Yellow, Color.Green]; + + PathGradientBrush Create() => new(null, colors, Color.White); + + Assert.Throws(Create); + } + + [Fact] + public void FillPathGradientBrushShouldThrowArgumentOutOfRangeExceptionWhenLessThan3PointsAreGiven() + { + PointF[] points = [new(0, 0), new(10, 0)]; + Color[] colors = [Color.Black, Color.Red, Color.Yellow, Color.Green]; + + PathGradientBrush Create() => new(points, colors, Color.White); + + Assert.Throws(Create); + } + + [Fact] + public void FillPathGradientBrushShouldThrowArgumentNullExceptionWhenColorsAreNull() + { + PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; + + PathGradientBrush Create() => new(points, null, Color.White); + + Assert.Throws(Create); + } + + [Fact] + public void FillPathGradientBrushShouldThrowArgumentOutOfRangeExceptionWhenEmptyColorArrayIsGiven() + { + PointF[] points = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; + Color[] colors = []; + + PathGradientBrush Create() => new(points, colors, Color.White); + + Assert.Throws(Create); + } + + [Theory] + [WithBlankImage(100, 100, PixelTypes.Rgba32)] + public void FillPathGradientBrushFillComplex(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + new TolerantImageComparer(0.2f), + image => + { + Star star = new(50, 50, 5, 20, 45); + PointF[] points = star.Points.ToArray(); + Color[] colors = + [ + Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Purple, + Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Purple + ]; + + PathGradientBrush brush = new(points, colors, Color.White); + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.Fill(brush))); + }, + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.PatternBrushes.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.PatternBrushes.cs new file mode 100644 index 000000000..43f43f17b --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.PatternBrushes.cs @@ -0,0 +1,285 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithPercent10(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen } + }; + + VerifyFloodFillPattern(provider, Brushes.Percent10(Color.HotPink, Color.LimeGreen), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithPercent10Transparent(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue }, + { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, + { Color.Blue, Color.Blue, Color.HotPink, Color.Blue }, + { Color.Blue, Color.Blue, Color.Blue, Color.Blue } + }; + + VerifyFloodFillPattern(provider, Brushes.Percent10(Color.HotPink), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithPercent20(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen }, + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen } + }; + + VerifyFloodFillPattern(provider, Brushes.Percent20(Color.HotPink, Color.LimeGreen), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithPercent20Transparent(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue }, + { Color.Blue, Color.Blue, Color.HotPink, Color.Blue }, + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue }, + { Color.Blue, Color.Blue, Color.HotPink, Color.Blue } + }; + + VerifyFloodFillPattern(provider, Brushes.Percent20(Color.HotPink), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithHorizontal(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.HotPink, Color.HotPink, Color.HotPink, Color.HotPink }, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen } + }; + + VerifyFloodFillPattern(provider, Brushes.Horizontal(Color.HotPink, Color.LimeGreen), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithHorizontalTransparent(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, + { Color.HotPink, Color.HotPink, Color.HotPink, Color.HotPink }, + { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, + { Color.Blue, Color.Blue, Color.Blue, Color.Blue } + }; + + VerifyFloodFillPattern(provider, Brushes.Horizontal(Color.HotPink), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithMin(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.HotPink, Color.HotPink, Color.HotPink, Color.HotPink } + }; + + VerifyFloodFillPattern(provider, Brushes.Min(Color.HotPink, Color.LimeGreen), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithMinTransparent(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, + { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, + { Color.Blue, Color.Blue, Color.Blue, Color.Blue }, + { Color.HotPink, Color.HotPink, Color.HotPink, Color.HotPink } + }; + + VerifyFloodFillPattern(provider, Brushes.Min(Color.HotPink), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithVertical(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen } + }; + + VerifyFloodFillPattern(provider, Brushes.Vertical(Color.HotPink, Color.LimeGreen), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithVerticalTransparent(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue } + }; + + VerifyFloodFillPattern(provider, Brushes.Vertical(Color.HotPink), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithForwardDiagonal(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink }, + { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen }, + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen } + }; + + VerifyFloodFillPattern(provider, Brushes.ForwardDiagonal(Color.HotPink, Color.LimeGreen), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithForwardDiagonalTransparent(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.Blue, Color.Blue, Color.Blue, Color.HotPink }, + { Color.Blue, Color.Blue, Color.HotPink, Color.Blue }, + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue } + }; + + VerifyFloodFillPattern(provider, Brushes.ForwardDiagonal(Color.HotPink), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithBackwardDiagonal(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen }, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink } + }; + + VerifyFloodFillPattern(provider, Brushes.BackwardDiagonal(Color.HotPink, Color.LimeGreen), expectedPattern); + } + + [Theory] + [WithBlankImage(20, 20, PixelTypes.Rgba32)] + public void FillPatternBrushImageShouldBeFloodFilledWithBackwardDiagonalTransparent(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color[,] expectedPattern = new Color[,] + { + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue }, + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue }, + { Color.Blue, Color.Blue, Color.HotPink, Color.Blue }, + { Color.Blue, Color.Blue, Color.Blue, Color.HotPink } + }; + + VerifyFloodFillPattern(provider, Brushes.BackwardDiagonal(Color.HotPink), expectedPattern); + } + + private static void VerifyFloodFillPattern( + TestImageProvider provider, + Brush brush, + Color[,] expectedPattern) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + ImageComparer.Exact, + image => + { + image.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.Blue)); + canvas.Fill(brush); + })); + + AssertPattern(image, expectedPattern); + }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + private static void AssertPattern(Image image, Color[,] expectedPattern) + where TPixel : unmanaged, IPixel + { + int rows = expectedPattern.GetLength(0); + int columns = expectedPattern.GetLength(1); + + TPixel[,] expectedPixels = new TPixel[rows, columns]; + for (int y = 0; y < rows; y++) + { + for (int x = 0; x < columns; x++) + { + expectedPixels[y, x] = expectedPattern[y, x].ToPixel(); + } + } + + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) + { + Span row = pixels.DangerousGetRowSpan(y); + int patternY = y % rows; + for (int x = 0; x < image.Width; x++) + { + TPixel expected = expectedPixels[patternY, x % columns]; + Assert.Equal(expected, row[x]); + } + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Polygons.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Polygons.cs new file mode 100644 index 000000000..b4cc8cdee --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Polygons.cs @@ -0,0 +1,421 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + public static TheoryData FillPolygon_Complex_Data { get; } = + new() + { + { false, IntersectionRule.EvenOdd }, + { false, IntersectionRule.NonZero }, + { true, IntersectionRule.EvenOdd }, + { true, IntersectionRule.NonZero }, + }; + + public static readonly TheoryData FillPolygon_EllipsePolygon_Data = + new() + { + { false, IntersectionRule.EvenOdd }, + { false, IntersectionRule.NonZero }, + { true, IntersectionRule.EvenOdd }, + { true, IntersectionRule.NonZero }, + }; + + [Theory] + [WithSolidFilledImages(8, 12, nameof(Color.Black), PixelTypes.Rgba32, 0)] + [WithSolidFilledImages(8, 12, nameof(Color.Black), PixelTypes.Rgba32, 8)] + [WithSolidFilledImages(8, 12, nameof(Color.Black), PixelTypes.Rgba32, 16)] + public void FillPolygon_Solid_Basic(TestImageProvider provider, int antialias) + where TPixel : unmanaged, IPixel + { + PointF[] polygon1 = PolygonFactory.CreatePointArray((2, 2), (6, 2), (6, 4), (2, 4)); + PointF[] polygon2 = PolygonFactory.CreatePointArray((2, 8), (4, 6), (6, 8), (4, 10)); + Polygon shape1 = new(new LinearLineSegment(polygon1)); + Polygon shape2 = new(new LinearLineSegment(polygon2)); + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = antialias > 0 } + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => + { + canvas.Fill(Brushes.Solid(Color.White), shape1); + canvas.Fill(Brushes.Solid(Color.White), shape2); + }), + testOutputDetails: $"aa{antialias}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, true)] + public void FillPolygon_Solid(TestImageProvider provider, string colorName, float alpha, bool antialias) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) + ]; + Polygon polygon = new(new LinearLineSegment(simplePath)); + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = antialias } + }; + + string aa = antialias ? string.Empty : "_NoAntialias"; + FormattableString outputDetails = $"{colorName}_A{alpha}{aa}"; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(color), polygon)), + outputDetails, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] + public void FillPolygon_Solid_Transformed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) + ]; + Polygon polygon = new(new LinearLineSegment(simplePath)); + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(-15), 0, new Vector2(200, 200))) + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(Color.White), polygon))); + } + + [Theory] + [WithBasicTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void FillPolygon_RectangularPolygon_Solid_Transformed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + RectangularPolygon polygon = new(25, 25, 50, 50); + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation((float)Math.PI / 4, new PointF(50, 50))) + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(Color.White), polygon))); + } + + [Theory] + [WithBasicTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void FillPolygon_RectangularPolygon_Solid_TransformedUsingConfiguration(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + RectangularPolygon polygon = new(25, 25, 50, 50); + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation((float)Math.PI / 4, new PointF(50, 50))) + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(Color.White), polygon))); + } + + [Theory] + [WithBasicTestPatternImages(nameof(FillPolygon_Complex_Data), 100, 100, PixelTypes.Rgba32)] + public void FillPolygon_Complex(TestImageProvider provider, bool reverse, IntersectionRule intersectionRule) + where TPixel : unmanaged, IPixel + { + PointF[] contour = PolygonFactory.CreatePointArray((20, 20), (80, 20), (80, 80), (20, 80)); + PointF[] hole = PolygonFactory.CreatePointArray((40, 40), (40, 60), (60, 60), (60, 40)); + + if (reverse) + { + Array.Reverse(contour); + Array.Reverse(hole); + } + + ComplexPolygon polygon = new( + new Path(new LinearLineSegment(contour)), + new Path(new LinearLineSegment(hole))); + + DrawingOptions options = new() + { + ShapeOptions = new ShapeOptions { IntersectionRule = intersectionRule } + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(Color.White), polygon)), + testOutputDetails: $"Reverse({reverse})_IntersectionRule({intersectionRule})", + comparer: ImageComparer.TolerantPercentage(0.01f), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, false)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, true)] + public void FillPolygon_Concave(TestImageProvider provider, bool reverse) + where TPixel : unmanaged, IPixel + { + PointF[] points = + [ + new Vector2(8, 8), + new Vector2(64, 8), + new Vector2(64, 64), + new Vector2(120, 64), + new Vector2(120, 120), + new Vector2(8, 120) + ]; + if (reverse) + { + Array.Reverse(points); + } + + Polygon polygon = new(new LinearLineSegment(points)); + Color color = Color.LightGreen; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(color), polygon)), + testOutputDetails: $"Reverse({reverse})", + comparer: ImageComparer.TolerantPercentage(0.01f), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(64, 64, "Black", PixelTypes.Rgba32)] + public void FillPolygon_StarCircle(TestImageProvider provider) + { + EllipsePolygon circle = new(32, 32, 30); + Star star = new(32, 32, 7, 10, 27); + IPath shape = circle.Clip(star); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(Color.White), shape)), + comparer: ImageComparer.TolerantPercentage(0.01f), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(128, 128, "Black", PixelTypes.Rgba32, BooleanOperation.Intersection)] + [WithSolidFilledImages(128, 128, "Black", PixelTypes.Rgba32, BooleanOperation.Union)] + [WithSolidFilledImages(128, 128, "Black", PixelTypes.Rgba32, BooleanOperation.Difference)] + [WithSolidFilledImages(128, 128, "Black", PixelTypes.Rgba32, BooleanOperation.Xor)] + public void FillPolygon_StarCircle_AllOperations(TestImageProvider provider, BooleanOperation operation) + { + IPath circle = new EllipsePolygon(36, 36, 36).Translate(28, 28); + Star star = new(64, 64, 5, 24, 64); + + // See http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/ClipType.htm for reference. + ShapeOptions shapeOptions = new() { BooleanOperation = operation }; + IPath shape = star.Clip(shapeOptions, circle); + DrawingOptions options = new() { ShapeOptions = shapeOptions }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => + { + canvas.Fill(Brushes.Solid(Color.DeepPink), circle); + canvas.Fill(Brushes.Solid(Color.LightGray), star); + canvas.Fill(Brushes.Solid(Color.ForestGreen), shape); + }), + testOutputDetails: operation.ToString(), + comparer: ImageComparer.TolerantPercentage(0.01F), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] + public void FillPolygon_Pattern(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) + ]; + Polygon polygon = new(new LinearLineSegment(simplePath)); + PatternBrush brush = Brushes.Horizontal(Color.Yellow); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.Fill(brush, polygon)), + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)] + public void FillPolygon_ImageBrush(TestImageProvider provider, string brushImageName) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200) + ]; + Polygon polygon = new(new LinearLineSegment(simplePath)); + + using Image brushImage = Image.Load(TestFile.Create(brushImageName).Bytes); + ImageBrush brush = new(brushImage); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.Fill(brush, polygon)), + System.IO.Path.GetFileNameWithoutExtension(brushImageName), + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)] + public void FillPolygon_ImageBrush_Rect(TestImageProvider provider, string brushImageName) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200) + ]; + Polygon polygon = new(new LinearLineSegment(simplePath)); + + using Image brushImage = Image.Load(TestFile.Create(brushImageName).Bytes); + + float top = brushImage.Height / 4F; + float left = brushImage.Width / 4F; + float height = top * 2; + float width = left * 2; + + ImageBrush brush = new(brushImage, new RectangleF(left, top, width, height)); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.Fill(brush, polygon)), + System.IO.Path.GetFileNameWithoutExtension(brushImageName) + "_rect", + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 250, PixelTypes.Rgba32)] + public void FillPolygon_RectangularPolygon(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + RectangularPolygon polygon = new(10, 10, 190, 140); + Color color = Color.White; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(color), polygon)), + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 50, 0f)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, 20f)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, -180f)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 5, 70, 0f)] + [WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 7, 80, -180f)] + public void FillPolygon_RegularPolygon(TestImageProvider provider, int vertices, float radius, float angleDeg) + where TPixel : unmanaged, IPixel + { + float angle = GeometryUtilities.DegreeToRadian(angleDeg); + RegularPolygon polygon = new(100, 100, vertices, radius, angle); + Color color = Color.Yellow; + + FormattableString testOutput = $"V({vertices})_R({radius})_Ang({angleDeg})"; + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(color), polygon)), + testOutput, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(nameof(FillPolygon_EllipsePolygon_Data), 200, 200, PixelTypes.Rgba32)] + public void FillPolygon_EllipsePolygon(TestImageProvider provider, bool reverse, IntersectionRule intersectionRule) + where TPixel : unmanaged, IPixel + { + IPath polygon = new EllipsePolygon(100, 100, 80, 120); + if (reverse) + { + polygon = polygon.Reverse(); + } + + Color color = Color.Azure; + DrawingOptions options = new() + { + ShapeOptions = new ShapeOptions { IntersectionRule = intersectionRule } + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(color), polygon)), + testOutputDetails: $"Reverse({reverse})_IntersectionRule({intersectionRule})", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(60, 60, "Blue", PixelTypes.Rgba32)] + public void FillPolygon_IntersectionRules_OddEven(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Polygon poly = new(new LinearLineSegment( + new PointF(10, 30), + new PointF(10, 20), + new PointF(50, 20), + new PointF(50, 50), + new PointF(20, 50), + new PointF(20, 10), + new PointF(30, 10), + new PointF(30, 40), + new PointF(40, 40), + new PointF(40, 30), + new PointF(10, 30))); + + DrawingOptions options = new() + { + ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.EvenOdd } + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(Color.HotPink), poly)), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(60, 60, "Blue", PixelTypes.Rgba32)] + public void FillPolygon_IntersectionRules_Nonzero(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Polygon poly = new(new LinearLineSegment( + new PointF(10, 30), + new PointF(10, 20), + new PointF(50, 20), + new PointF(50, 50), + new PointF(20, 50), + new PointF(20, 10), + new PointF(30, 10), + new PointF(30, 40), + new PointF(40, 40), + new PointF(40, 30), + new PointF(10, 30))); + + DrawingOptions options = new() + { + ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.NonZero } + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(Color.HotPink), poly)), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Primitives.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Primitives.cs new file mode 100644 index 000000000..35c03cc03 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Primitives.cs @@ -0,0 +1,637 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + public static readonly TheoryData DrawBezierData = + new() + { + { "White", 255, 1.5F }, + { "Red", 255, 3F }, + { "HotPink", 255, 5F }, + { "HotPink", 150, 5F }, + { "White", 255, 15F }, + }; + + public static readonly TheoryData DrawPathData = + new() + { + { "White", 255, 1.5F }, + { "Red", 255, 3F }, + { "HotPink", 255, 5F }, + { "HotPink", 150, 5F }, + { "White", 255, 15F }, + }; + + [Theory] + [WithSolidFilledImages(nameof(DrawBezierData), 300, 450, "Blue", PixelTypes.Rgba32)] + public void DrawBeziers(TestImageProvider provider, string colorName, byte alpha, float thickness) + where TPixel : unmanaged, IPixel + { + PointF[] points = + [ + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + ]; + + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha / 255F); + FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; + DrawingOptions options = new(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.DrawBezier(Pens.Solid(color, 5F), points))); + image.DebugSave( + provider, + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.001F), + provider, + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBlankImage(500, 500, PixelTypes.Rgba32)] + public void SolidBezierFilledBezier(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + ]; + + Polygon polygon = new(new CubicBezierLineSegment(simplePath)); + SolidBrush brush = Brushes.Solid(Color.HotPink); + + provider.RunValidatingProcessorTest( + ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Clear(Brushes.Solid(Color.Blue)); + canvas.Fill(brush, polygon); + })); + } + + [Theory] + [WithBlankImage(500, 500, PixelTypes.Rgba32)] + public void SolidBezierOverlayByFilledPolygonOpacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + ]; + + Polygon polygon = new(new CubicBezierLineSegment(simplePath)); + SolidBrush brush = Brushes.Solid(Color.HotPink.WithAlpha(150 / 255F)); + + provider.RunValidatingProcessorTest( + ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Clear(Brushes.Solid(Color.Blue)); + canvas.Fill(brush, polygon); + })); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1F, 2.5F, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6F, 10F, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1F, 5F, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1F, 10F, true)] + public void DrawLines_Simple(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + SolidPen pen = new(color, thickness); + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithSolidFilledImages(30, 30, "White", PixelTypes.Rgba32, 1F, true)] + [WithSolidFilledImages(30, 30, "White", PixelTypes.Rgba32, 5F, true)] + [WithSolidFilledImages(30, 30, "White", PixelTypes.Rgba32, 1F, false)] + [WithSolidFilledImages(30, 30, "White", PixelTypes.Rgba32, 5F, false)] + public void DrawLinesInvalidPoints(TestImageProvider provider, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + SolidPen pen = new(Color.Black, thickness); + PointF[] path = [new Vector2(15F, 15F), new Vector2(15F, 15F)]; + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = antialias } + }; + + string aa = antialias ? string.Empty : "_NoAntialias"; + FormattableString outputDetails = $"T({thickness}){aa}"; + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.DrawLine(pen, path))); + image.DebugSave(provider, outputDetails, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.001F), + provider, + outputDetails, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1F, 5F, false)] + public void DrawLines_Dash(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = Pens.Dash(color, thickness); + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "LightGreen", 1F, 5F, false)] + public void DrawLines_Dot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = Pens.Dot(color, thickness); + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1F, 5F, false)] + public void DrawLines_DashDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = Pens.DashDot(color, thickness); + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Black", 1F, 5F, false)] + public void DrawLines_DashDotDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + Pen pen = Pens.DashDotDot(color, thickness); + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1F, 5F, true)] + public void DrawLines_EndCapRound(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + PatternPen pen = new(new PenOptions(color, thickness, [3F, 3F]) + { + StrokeOptions = new StrokeOptions { LineCap = LineCap.Round }, + }); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1F, 5F, true)] + public void DrawLines_EndCapButt(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + PatternPen pen = new(new PenOptions(color, thickness, [3F, 3F]) + { + StrokeOptions = new StrokeOptions { LineCap = LineCap.Butt }, + }); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1F, 5F, true)] + public void DrawLines_EndCapSquare(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + PatternPen pen = new(new PenOptions(color, thickness, [3F, 3F]) + { + StrokeOptions = new StrokeOptions { LineCap = LineCap.Square }, + }); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1F, 10F, true)] + public void DrawLines_JointStyleRound(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + SolidPen pen = new(new PenOptions(color, thickness) + { + StrokeOptions = new StrokeOptions { LineJoin = LineJoin.Round }, + }); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1F, 10F, true)] + public void DrawLines_JointStyleSquare(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + SolidPen pen = new(new PenOptions(color, thickness) + { + StrokeOptions = new StrokeOptions { LineJoin = LineJoin.Bevel }, + }); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1F, 10F, true)] + public void DrawLines_JointStyleMiter(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + SolidPen pen = new(new PenOptions(color, thickness) + { + StrokeOptions = new StrokeOptions { LineJoin = LineJoin.Miter }, + }); + + DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, true, false, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, true, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, true)] + public void DrawComplexPolygon(TestImageProvider provider, bool overlap, bool transparent, bool dashed) + where TPixel : unmanaged, IPixel + { + Polygon simplePath = new(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + Polygon hole1 = new(new LinearLineSegment( + new Vector2(37, 85), + overlap ? new Vector2(130, 40) : new Vector2(93, 85), + new Vector2(65, 137))); + + IPath clipped = simplePath.Clip(hole1); + + Color color = Color.White; + if (transparent) + { + color = color.WithAlpha(150 / 255F); + } + + string testDetails = string.Empty; + if (overlap) + { + testDetails += "_Overlap"; + } + + if (transparent) + { + testDetails += "_Transparent"; + } + + if (dashed) + { + testDetails += "_Dashed"; + } + + Pen pen = dashed ? Pens.Dash(color, 5F) : Pens.Solid(color, 5F); + DrawingOptions options = new(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Draw(pen, clipped))); + image.DebugSave( + provider, + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + provider, + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, false)] + [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, true, false)] + [WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, true)] + public void FillComplexPolygon_SolidFill(TestImageProvider provider, bool overlap, bool transparent) + where TPixel : unmanaged, IPixel + { + Polygon simplePath = new(new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300))); + + Polygon hole1 = new(new LinearLineSegment( + new Vector2(37, 85), + overlap ? new Vector2(130, 40) : new Vector2(93, 85), + new Vector2(65, 137))); + + IPath clipped = simplePath.Clip(hole1); + + Color color = Color.HotPink; + if (transparent) + { + color = color.WithAlpha(150 / 255F); + } + + string testDetails = string.Empty; + if (overlap) + { + testDetails += "_Overlap"; + } + + if (transparent) + { + testDetails += "_Transparent"; + } + + DrawingOptions options = new(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(color), clipped))); + image.DebugSave( + provider, + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.001F), + provider, + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1F, 2.5F, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6F, 10F, true)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1F, 5F, false)] + [WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1F, 10F, true)] + public void DrawPolygon(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + ]; + + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); + IPath polygon = new Polygon(new LinearLineSegment(simplePath)); + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = antialias } + }; + + string aa = antialias ? string.Empty : "_NoAntialias"; + FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Draw(Pens.Solid(color, thickness), polygon))); + image.DebugSave(provider, outputDetails, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.001F), + provider, + outputDetails, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] + public void DrawPolygon_Transformed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = + [ + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + ]; + + IPath polygon = new Polygon(new LinearLineSegment(simplePath)); + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateSkew( + GeometryUtilities.DegreeToRadian(-15), + 0, + new Vector2(200, 200))) + }; + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Draw(Pens.Solid(Color.White, 2.5F), polygon))); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.001F), provider); + } + + [Theory] + [WithBasicTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void DrawPolygonRectangular_Transformed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + RectangularPolygon polygon = new(25, 25, 50, 50); + DrawingOptions options = new() + { + Transform = new Matrix4x4(Matrix3x2.CreateRotation((float)Math.PI / 4, new PointF(50, 50))) + }; + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Draw(Pens.Solid(Color.White, 2.5F), polygon))); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.001F), provider); + } + + [Theory] + [WithSolidFilledImages(nameof(DrawPathData), 300, 600, "Blue", PixelTypes.Rgba32)] + public void DrawPath(TestImageProvider provider, string colorName, byte alpha, float thickness) + where TPixel : unmanaged, IPixel + { + LinearLineSegment linearSegment = new( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300)); + CubicBezierLineSegment bezierSegment = new( + new Vector2(50, 300), + new Vector2(500, 500), + new Vector2(60, 10), + new Vector2(10, 400)); + + ArcLineSegment ellipticArcSegment1 = new(new Vector2(10, 400), new Vector2(150, 450), new SizeF((float)Math.Sqrt(5525), 40), GeometryUtilities.RadianToDegree((float)Math.Atan2(25, 70)), true, true); + ArcLineSegment ellipticArcSegment2 = new(new PointF(150, 450), new PointF(149F, 450), new SizeF(140, 70), 0, true, true); + Path path = new(linearSegment, bezierSegment, ellipticArcSegment1, ellipticArcSegment2); + + Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha / 255F); + FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; + DrawingOptions options = new(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Draw(Pens.Solid(color, thickness), path))); + image.DebugSave( + provider, + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.001F), + provider, + testDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(256, 256, "Black", PixelTypes.Rgba32)] + public void DrawPathExtendingOffEdgeOfImageShouldNotBeCropped(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + SolidPen pen = Pens.Solid(Color.White, 5F); + DrawingOptions options = new(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => + { + for (int i = 0; i < 300; i += 20) + { + PointF[] points = [new Vector2(100, 2), new Vector2(-10, i)]; + canvas.DrawLine(pen, points); + } + })); + image.DebugSave( + provider, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.001F), + provider, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(40, 40, "White", PixelTypes.Rgba32)] + public void DrawPathClippedOnTop(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + PointF[] points = + [ + new(10F, -10F), + new(20F, 20F), + new(30F, -30F) + ]; + + IPath path = new PathBuilder().AddLines(points).Build(); + DrawingOptions options = new(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Draw(Pens.Solid(Color.Black, 1F), path))); + image.DebugSave( + provider, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + provider, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 360F)] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 359F)] + public void DrawPathCircleUsingAddArc(TestImageProvider provider, float sweep) + where TPixel : unmanaged, IPixel + { + IPath path = new PathBuilder().AddArc(new Point(150, 150), 50, 50, 0, 40, sweep).Build(); + DrawingOptions options = new(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Draw(Pens.Solid(Color.Black, 1F), path))); + image.DebugSave( + provider, + $"{sweep}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + provider, + $"{sweep}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, true)] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, false)] + public void DrawPathCircleUsingArcTo(TestImageProvider provider, bool sweep) + where TPixel : unmanaged, IPixel + { + Point origin = new(150, 150); + IPath path = new PathBuilder().MoveTo(origin).ArcTo(50, 50, 0, true, sweep, origin).Build(); + DrawingOptions options = new(); + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.Draw(Pens.Solid(Color.Black, 1F), path))); + image.DebugSave( + provider, + $"{sweep}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + provider, + $"{sweep}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + private static void DrawLinesImpl( + TestImageProvider provider, + string colorName, + float alpha, + float thickness, + bool antialias, + Pen pen) + where TPixel : unmanaged, IPixel + { + PointF[] simplePath = [new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)]; + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = antialias } + }; + + string aa = antialias ? string.Empty : "_NoAntialias"; + FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; + + using Image image = provider.GetImage(); + image.Mutate(ctx => ctx.ProcessWithCanvas(options, canvas => canvas.DrawLine(pen, simplePath))); + image.DebugSave(provider, outputDetails, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.001F), + provider, + outputDetails, + appendSourceFileOrDescription: false); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Recolor.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Recolor.cs new file mode 100644 index 000000000..c8adc0674 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Recolor.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, "Yellow", "Pink", 0.2f)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] + [WithTestPatternImage(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] + [WithTestPatternImage(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.6f)] + public void RecolorImage(TestImageProvider provider, string sourceColorName, string targetColorName, float threshold) + where TPixel : unmanaged, IPixel + { + Color sourceColor = TestUtils.GetColorByName(sourceColorName); + Color targetColor = TestUtils.GetColorByName(targetColorName); + RecolorBrush brush = new(sourceColor, targetColor, threshold); + + FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; + provider.RunValidatingProcessorTest(x => x.ProcessWithCanvas(canvas => canvas.Fill(brush)), testInfo); + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] + [WithTestPatternImage(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] + public void RecolorImage_InBox(TestImageProvider provider, string sourceColorName, string targetColorName, float threshold) + where TPixel : unmanaged, IPixel + { + Color sourceColor = TestUtils.GetColorByName(sourceColorName); + Color targetColor = TestUtils.GetColorByName(targetColorName); + RecolorBrush brush = new(sourceColor, targetColor, threshold); + + FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(canvas => + { + Rectangle bounds = canvas.Bounds; + Rectangle region = new(0, (bounds.Height / 2) - (bounds.Height / 4), bounds.Width, bounds.Height / 2); + canvas.Fill(brush, region); + }), + testInfo); + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Robustness.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Robustness.cs new file mode 100644 index 000000000..0bd59b8c4 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Robustness.cs @@ -0,0 +1,376 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +#pragma warning disable xUnit1004 // Test methods should not be skipped +using System.Numerics; +using System.Runtime.InteropServices; +using System.Linq; +using GeoJSON.Net.Feature; +using Newtonsoft.Json; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SkiaSharp; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + [Theory(Skip = "For local testing")] + [WithSolidFilledImages(32, 32, "Black", PixelTypes.Rgba32)] + public void CompareToSkiaResults_SmallCircle(TestImageProvider provider) + { + EllipsePolygon circle = new(16, 16, 10); + + CompareToSkiaResultsImpl(provider, circle); + } + + [Theory(Skip = "For local testing")] + [WithSolidFilledImages(64, 64, "Black", PixelTypes.Rgba32)] + public void CompareToSkiaResults_StarCircle(TestImageProvider provider) + { + EllipsePolygon circle = new(32, 32, 30); + Star star = new(32, 32, 7, 10, 27); + IPath shape = circle.Clip(star); + + CompareToSkiaResultsImpl(provider, shape); + } + + private static void CompareToSkiaResultsImpl(TestImageProvider provider, IPath shape) + { + using Image image = provider.GetImage(); + image.Mutate(c => c.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(Color.White), shape))); + image.DebugSave(provider, "ImageSharp", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + using SKBitmap bitmap = new(new SKImageInfo(image.Width, image.Height)); + + using SKPath skPath = new(); + + foreach (ISimplePath loop in shape.Flatten()) + { + ReadOnlySpan points = MemoryMarshal.Cast(loop.Points.Span); + skPath.AddPoly(points.ToArray()); + } + + using SKPaint paint = new() + { + Style = SKPaintStyle.Fill, + Color = SKColors.White, + IsAntialias = true, + }; + + using SKCanvas canvas = new(bitmap); + canvas.Clear(new SKColor(0, 0, 0)); + canvas.DrawPath(skPath, paint); + + using Image skResultImage = + Image.LoadPixelData(bitmap.GetPixelSpan(), image.Width, image.Height); + skResultImage.DebugSave( + provider, + "SkiaSharp", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + ImageSimilarityReport result = ImageComparer.Exact.CompareImagesOrFrames(image, skResultImage); + throw new ImagesSimilarityException(result.DifferencePercentageString); + } + + [Theory(Skip = "For local testing")] + [WithSolidFilledImages(3600, 2400, "Black", PixelTypes.Rgba32, TestImages.GeoJson.States, 16, 30, 30)] + public void LargeGeoJson_Lines(TestImageProvider provider, string geoJsonFile, int aa, float sx, float sy) + { + string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(geoJsonFile)); + + PointF[][] points = PolygonFactory.GetGeoJsonPoints(jsonContent, Matrix4x4.CreateScale(sx, sy, 1)); + + using Image image = provider.GetImage(); + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = aa > 0 }, + }; + + image.Mutate(c => c.ProcessWithCanvas(options, canvas => + { + Pen pen = Pens.Solid(Color.White, 1.0F); + foreach (PointF[] loop in points) + { + canvas.DrawLine(pen, loop); + } + })); + + string details = $"_{System.IO.Path.GetFileName(geoJsonFile)}_{sx}x{sy}_aa{aa}"; + + image.DebugSave( + provider, + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(7200, 3300, "Black", PixelTypes.Rgba32)] + public void LargeGeoJson_States_Fill(TestImageProvider provider) + { + using Image image = FillGeoJsonPolygons(provider, TestImages.GeoJson.States, true, new Vector2(60), new Vector2(0, -1000)); + ImageComparer comparer = ImageComparer.TolerantPercentage(0.001f); + + image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput(comparer, provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + } + + private static Image FillGeoJsonPolygons(TestImageProvider provider, string geoJsonFile, bool aa, Vector2 scale, Vector2 pixelOffset) + { + string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(geoJsonFile)); + + PointF[][] points = PolygonFactory.GetGeoJsonPoints(jsonContent, Matrix4x4.CreateScale(scale.X, scale.Y, 1) * Matrix4x4.CreateTranslation(pixelOffset.X, pixelOffset.Y, 0)); + + Image image = provider.GetImage(); + DrawingOptions options = new() + { + GraphicsOptions = new GraphicsOptions { Antialias = aa }, + }; + Random rnd = new(42); + byte[] rgb = new byte[3]; + + image.Mutate(c => c.ProcessWithCanvas(options, canvas => + { + foreach (PointF[] loop in points) + { + rnd.NextBytes(rgb); + + Color color = Color.FromPixel(new Rgb24(rgb[0], rgb[1], rgb[2])); + canvas.Fill(Brushes.Solid(color), new Polygon(new LinearLineSegment(loop))); + } + })); + + return image; + } + + [Theory] + [WithSolidFilledImages(400, 400, "Black", PixelTypes.Rgba32, 0)] + [WithSolidFilledImages(6000, 6000, "Black", PixelTypes.Rgba32, 5500)] + public void LargeGeoJson_Mississippi_Lines(TestImageProvider provider, int pixelOffset) + { + string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); + + FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); + + Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); + + Matrix4x4 transform = Matrix4x4.CreateTranslation(-87, -54, 0) + * Matrix4x4.CreateScale(60, 60, 1) + * Matrix4x4.CreateTranslation(pixelOffset, pixelOffset, 0); + IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); + + using Image image = provider.GetImage(); + image.Mutate(c => c.ProcessWithCanvas(canvas => + { + Pen pen = Pens.Solid(Color.White, 1.0F); + foreach (PointF[] loop in points) + { + canvas.DrawLine(pen, loop); + } + })); + + // Strict comparer, because the image is sparse: + ImageComparer comparer = ImageComparer.TolerantPercentage(0.0001F); + + string details = $"PixelOffset({pixelOffset})"; + image.DebugSave(provider, details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput(comparer, provider, testOutputDetails: details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + } + + [Theory] + [WithSolidFilledImages(400 * 3, 400 * 3, "Black", PixelTypes.Rgba32, 3)] + [WithSolidFilledImages(400 * 5, 400 * 5, "Black", PixelTypes.Rgba32, 5)] + [WithSolidFilledImages(400 * 10, 400 * 10, "Black", PixelTypes.Rgba32, 10)] + public void LargeGeoJson_Mississippi_LinesScaled(TestImageProvider provider, int scale) + { + string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); + + FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); + + Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); + + Matrix4x4 transform = Matrix4x4.CreateTranslation(-87, -54, 0) + * Matrix4x4.CreateScale(60, 60, 1); + IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); + + using Image image = provider.GetImage(); + SolidPen pen = new(new SolidBrush(Color.White), 1.0f); + + image.Mutate(c => c.ProcessWithCanvas(canvas => + { + foreach (PointF[] loop in points) + { + IPath outline = pen.GeneratePath(new Path(loop).Transform(Matrix4x4.CreateTranslation(0.5F, 0.5F, 0))); + outline = outline.Transform(Matrix4x4.CreateScale(scale, scale, 1)); + canvas.Fill(pen.StrokeFill, outline); + } + })); + + // Strict comparer, because the image is sparse: + ImageComparer comparer = ImageComparer.TolerantPercentage(0.0001F); + + string details = $"Scale({scale})"; + image.DebugSave(provider, details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput(comparer, provider, testOutputDetails: details, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + } + + [Theory(Skip = "For local experiments only")] + [InlineData(0)] + [InlineData(5000)] + [InlineData(9000)] + public void Missisippi_Skia(int offset) + { + string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); + + FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); + + Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); + + Matrix4x4 transform = Matrix4x4.CreateTranslation(-87, -54, 0) + * Matrix4x4.CreateScale(60, 60, 1) + * Matrix4x4.CreateTranslation(offset, offset, 0); + IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); + + SKPath path = new(); + + foreach (PointF[] pts in points.Where(p => p.Length > 2)) + { + path.MoveTo(pts[0].X, pts[0].Y); + + for (int i = 0; i < pts.Length; i++) + { + path.LineTo(pts[i].X, pts[i].Y); + } + + path.LineTo(pts[0].X, pts[0].Y); + } + + SKImageInfo imageInfo = new(10000, 10000); + + using SKPaint paint = new() + { + Style = SKPaintStyle.Stroke, + Color = SKColors.White, + StrokeWidth = 1f, + IsAntialias = true, + }; + + using SKSurface surface = SKSurface.Create(imageInfo); + SKCanvas canvas = surface.Canvas; + canvas.Clear(new SKColor(0, 0, 0)); + canvas.DrawPath(path, paint); + + string outDir = TestEnvironment.CreateOutputDirectory("Skia"); + string fn = System.IO.Path.Combine(outDir, $"Missisippi_Skia_{offset}.png"); + + using SKImage image = surface.Snapshot(); + using SKData data = image.Encode(SKEncodedImageFormat.Png, 100); + + using FileStream fs = File.Create(fn); + data.SaveTo(fs); + } + + [Theory(Skip = "For local experiments only")] + [WithSolidFilledImages(1000, 1000, "Black", PixelTypes.Rgba32, 10)] + public void LargeGeoJson_States_Separate_Benchmark(TestImageProvider provider, int thickness) + { + string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); + + FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); + + Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); + + Matrix4x4 transform = Matrix4x4.CreateTranslation(-87, -54, 0) * Matrix4x4.CreateScale(60, 60, 1); + IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); + + using Image image = provider.GetImage(); + + image.Mutate(c => c.ProcessWithCanvas(canvas => + { + Pen pen = Pens.Solid(Color.White, thickness); + foreach (PointF[] loop in points) + { + canvas.Draw(pen, new Polygon(new LinearLineSegment(loop))); + } + })); + + image.DebugSave(provider, $"Benchmark_{thickness}", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + } + + [Theory(Skip = "For local experiments only")] + [WithSolidFilledImages(1000, 1000, "Black", PixelTypes.Rgba32, 10)] + public void LargeGeoJson_States_All_Benchmark(TestImageProvider provider, int thickness) + { + string jsonContent = File.ReadAllText(TestFile.GetInputFileFullPath(TestImages.GeoJson.States)); + + FeatureCollection features = JsonConvert.DeserializeObject(jsonContent); + + Feature missisipiGeom = features.Features.Single(f => (string)f.Properties["NAME"] == "Mississippi"); + + Matrix4x4 transform = Matrix4x4.CreateTranslation(-87, -54, 0) * Matrix4x4.CreateScale(60, 60, 1); + IReadOnlyList points = PolygonFactory.GetGeoJsonPoints(missisipiGeom, transform); + + PathBuilder pb = new(); + foreach (PointF[] loop in points) + { + pb.StartFigure(); + pb.AddLines(loop); + pb.CloseFigure(); + } + + IPath path = pb.Build(); + + using Image image = provider.GetImage(); + + image.Mutate(c => c.ProcessWithCanvas(canvas => canvas.Draw(Pens.Solid(Color.White, thickness), path))); + + image.DebugSave(provider, $"Benchmark_{thickness}", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + } + + [Theory(Skip = "For local experiments only")] + [WithSolidFilledImages(1000, 1000, "Black", PixelTypes.Rgba32, 10)] + public void LargeStar_Benchmark(TestImageProvider provider, int thickness) + { + List points = CreateStarPolygon(1001, 100F); + Matrix4x4 transform = Matrix4x4.CreateTranslation(250, 250, 0); + DrawingOptions options = new() { Transform = transform }; + + using Image image = provider.GetImage(); + + image.Mutate(c => c.ProcessWithCanvas(options, canvas => + { + Pen pen = Pens.Solid(Color.White, thickness); + foreach (PointF[] loop in points) + { + canvas.Draw(pen, new Polygon(new LinearLineSegment(loop))); + } + })); + + image.DebugSave(provider, $"Benchmark_{thickness}", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + } + + private static List CreateStarPolygon(int vertexCount, float radius) + { + if (vertexCount < 5 || (vertexCount & 1) == 0) + { + throw new ArgumentOutOfRangeException(nameof(vertexCount), "Vertex count must be an odd number >= 5."); + } + + int step = (vertexCount - 1) / 2; + List contour = new(vertexCount + 1); + for (int i = 0; i < vertexCount; i++) + { + int index = (i * step) % vertexCount; + float angle = (index * MathF.PI * 2) / vertexCount; + contour.Add(new PointF(MathF.Cos(angle) * radius, MathF.Sin(angle) * radius)); + } + + contour.Add(contour[0]); + return [[.. contour]]; + } +} +#pragma warning restore xUnit1004 // Test methods should not be skipped diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.SvgPath.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.SvgPath.cs new file mode 100644 index 000000000..87063f20d --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.SvgPath.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + [Theory] + [WithBlankImage(110, 70, PixelTypes.Rgba32, "M20,30 L40,5 L60,30 L80, 55 L100, 30", "zag")] + [WithBlankImage(110, 50, PixelTypes.Rgba32, "M20,30 Q40,5 60,30 T100,30", "wave")] + [WithBlankImage(500, 400, PixelTypes.Rgba32, "M10,350 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25", "bumpy")] + [WithBlankImage(500, 400, PixelTypes.Rgba32, "M300,200 h-150 a150,150 0 1,0 150,-150 z", "pie_small")] + [WithBlankImage(500, 400, PixelTypes.Rgba32, "M275,175 v-150 a150,150 0 0,0 -150,150 z", "pie_big")] + [WithBlankImage(100, 100, PixelTypes.Rgba32, "M50,50 L50,20 L80,50 z M40,60 L40,90 L10,60 z", "arrows")] + [WithBlankImage(500, 400, PixelTypes.Rgba32, "M 10 315 L 110 215 A 30 50 0 0 1 162.55 162.45 L 172.55 152.45 A 30 50 -45 0 1 215.1 109.9 L 315 10", "chopped_oval")] + public void SvgPathRenderSvgPath(TestImageProvider provider, string svgPath, string exampleImageKey) + where TPixel : unmanaged, IPixel + { + bool parsed = Path.TryParseSvgPath(svgPath, out IPath path); + Assert.True(parsed); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.Draw(Pens.Solid(Color.Red, 5), path); + }), + new { type = exampleImageKey }, + comparer: ImageComparer.TolerantPercentage(0.0035F)); // NET 472 x86 requires higher percentage + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Text.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Text.cs new file mode 100644 index 000000000..19837768a --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.Text.cs @@ -0,0 +1,1060 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Text; +using SixLabors.Fonts; +using SixLabors.Fonts.Unicode; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Drawing.Text; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +public partial class ProcessWithDrawingCanvasTests +{ + private const string AB = "AB\nAB"; + + private const string TestText = "Sphinx of black quartz, judge my vow\n0123456789"; + + private static readonly ImageComparer TextDrawingComparer = ImageComparer.TolerantPercentage(1e-2f); + + private static readonly ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(0.0069F); + + [Theory] + [WithSolidFilledImages(1276, 336, "White", PixelTypes.Rgba32, ColorFontSupport.ColrV0)] + [WithSolidFilledImages(1276, 336, "White", PixelTypes.Rgba32, ColorFontSupport.None)] + public void EmojiFontRendering(TestImageProvider provider, ColorFontSupport colorFontSupport) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 70); + FontFamily emojiFontFamily = CreateFont(TestFonts.TwemojiMozilla, 36).Family; + + Color color = Color.Black; + string text = "A short piece of text 😀 with an emoji"; + + provider.VerifyOperation( + TextDrawingComparer, + img => + { + RichTextOptions textOptions = new(font) + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + FallbackFontFamilies = [emojiFontFamily], + ColorFontSupport = colorFontSupport, + Origin = new PointF(img.Width / 2, img.Height / 2) + }; + + img.Mutate(i => i.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, Brushes.Solid(color), pen: null))); + }, + $"ColorFontsEnabled-{colorFontSupport == ColorFontSupport.ColrV0}"); + } + + [Theory] + [WithSolidFilledImages(400, 200, "White", PixelTypes.Rgba32)] + public void FallbackFontRendering(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // https://github.com/SixLabors/Fonts/issues/171 + FontCollection collection = new(); + Font whitney = CreateFont(TestFonts.WhitneyBook, 25); + FontFamily malgun = CreateFont(TestFonts.Malgun, 25).Family; + + Color color = Color.Black; + const string text = "亞DARKSOUL亞"; + + provider.VerifyOperation( + TextDrawingComparer, + img => + { + RichTextOptions textOptions = new(whitney) + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + FallbackFontFamilies = [malgun], + KerningMode = KerningMode.Standard, + Origin = new PointF(img.Width / 2, img.Height / 2) + }; + + img.Mutate(i => i.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, Brushes.Solid(color), pen: null))); + }); + } + + [Theory] + [WithSolidFilledImages(276, 336, "White", PixelTypes.Rgba32)] + public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 36); + Color color = Color.Black; + const string text = "A short piece of text"; + + using Image img = provider.GetImage(); + + // Measure the text size + FontRectangle size = TextMeasurer.MeasureSize(text, new RichTextOptions(font)); + + // Find out how much we need to scale the text to fill the space (up or down) + float scalingFactor = Math.Min(img.Width / size.Width, img.Height / size.Height); + + // Create a new font + Font scaledFont = new(font, scalingFactor * font.Size); + RichTextOptions textOptions = new(scaledFont) + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + Origin = new PointF(img.Width / 2, img.Height / 2) + }; + + img.Mutate(i => i.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, Brushes.Solid(color), pen: null))); + } + + [Theory] + [WithSolidFilledImages(1500, 500, "White", PixelTypes.Rgba32)] + public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688_2(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 39); + string text = new('a', 10000); + Color color = Color.Black; + PointF point = new(100, 100); + + using Image img = provider.GetImage(); + img.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.DrawText( + CreateTextOptionsAt(font, point), + text, + Brushes.Solid(color), + pen: null))); + } + + [Theory] + [WithSolidFilledImages(200, 200, "White", PixelTypes.Rgba32)] + public void OpenSansJWithNoneZeroShouldntExtendPastGlyphe(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 50); + Color color = Color.Black; + + using Image img = provider.GetImage(); + img.Mutate(ctx => ctx.ProcessWithCanvas(canvas => canvas.DrawText( + CreateTextOptionsAt(font, new PointF(-50, 2)), + TestText, + Brushes.Solid(Color.Black), + pen: null))); + + Assert.Equal(Color.White.ToPixel(), img[173, 2]); + } + + [Theory] + [WithSolidFilledImages(20, 50, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.OpenSans, "i")] + [WithSolidFilledImages(200, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.SixLaborsSampleAB, AB)] + [WithSolidFilledImages(900, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.OpenSans, TestText)] + [WithSolidFilledImages(400, 45, "White", PixelTypes.Rgba32, 20, 0, 0, TestFonts.OpenSans, TestText)] + [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 50, TestFonts.OpenSans, TestText)] + public void FontShapesAreRenderedCorrectly( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(fontName, fontSize); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => canvas.DrawText( + CreateTextOptionsAt(font, new PointF(x, y)), + text, + Brushes.Solid(Color.Black), + pen: null)), + $"{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(50, 50, "White", PixelTypes.Rgba32, 50, 25, 25, TestFonts.OpenSans, "i", 45, 25, 25)] + [WithSolidFilledImages(200, 200, "White", PixelTypes.Rgba32, 50, 100, 100, TestFonts.SixLaborsSampleAB, AB, 45, 100, 100)] + [WithSolidFilledImages(1100, 1100, "White", PixelTypes.Rgba32, 50, 550, 550, TestFonts.OpenSans, TestText, 45, 550, 550)] + [WithSolidFilledImages(400, 400, "White", PixelTypes.Rgba32, 20, 200, 200, TestFonts.OpenSans, TestText, 45, 200, 200)] + public void FontShapesAreRenderedCorrectly_WithRotationApplied( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text, + float angle, + float rotationOriginX, + float rotationOriginY) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(fontName, fontSize); + float radians = GeometryUtilities.DegreeToRadian(angle); + + RichTextOptions textOptions = new(font) + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + Origin = new PointF(x, y) + }; + + Matrix4x4 transform = new(Matrix3x2.CreateRotation(radians, new Vector2(rotationOriginX, rotationOriginY))); + DrawingOptions drawingOptions = new() { Transform = transform }; + + provider.RunValidatingProcessorTest( + ctx => ctx.ProcessWithCanvas(drawingOptions, canvas => canvas.DrawText( + textOptions, + text, + Brushes.Solid(Color.Black), + pen: null)), + $"F({fontName})-S({fontSize})-A({angle})-{ToTestOutputDisplayText(text)}-({x},{y})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(50, 50, "White", PixelTypes.Rgba32, 50, 25, 25, TestFonts.OpenSans, "i", -12, 0, 25, 25)] + [WithSolidFilledImages(200, 200, "White", PixelTypes.Rgba32, 50, 100, 100, TestFonts.SixLaborsSampleAB, AB, 10, 0, 100, 100)] + [WithSolidFilledImages(1100, 1100, "White", PixelTypes.Rgba32, 50, 550, 550, TestFonts.OpenSans, TestText, 0, 10, 550, 550)] + [WithSolidFilledImages(400, 400, "White", PixelTypes.Rgba32, 20, 200, 200, TestFonts.OpenSans, TestText, 0, -10, 200, 200)] + public void FontShapesAreRenderedCorrectly_WithSkewApplied( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text, + float angleX, + float angleY, + float rotationOriginX, + float rotationOriginY) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(fontName, fontSize); + float radianX = GeometryUtilities.DegreeToRadian(angleX); + float radianY = GeometryUtilities.DegreeToRadian(angleY); + + RichTextOptions textOptions = new(font) + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + Origin = new PointF(x, y) + }; + + Matrix4x4 transform = new(Matrix3x2.CreateSkew(radianX, radianY, new Vector2(rotationOriginX, rotationOriginY))); + DrawingOptions drawingOptions = new() { Transform = transform }; + + provider.RunValidatingProcessorTest( + ctx => ctx.ProcessWithCanvas(drawingOptions, canvas => canvas.DrawText( + textOptions, + text, + Brushes.Solid(Color.Black), + pen: null)), + $"F({fontName})-S({fontSize})-A({angleX},{angleY})-{ToTestOutputDisplayText(text)}-({x},{y})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + /// + /// Based on: + /// https://github.com/SixLabors/ImageSharp/issues/572 + /// + [Theory] + [WithSolidFilledImages(2480, 3508, "White", PixelTypes.Rgba32)] + public void FontShapesAreRenderedCorrectly_LargeText( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 36); + + StringBuilder sb = new(); + string str = Repeat(" ", 78) + "THISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDS"; + sb.Append(str); + + string newLines = Repeat("\r\n", 61); + sb.Append(newLines); + + for (int i = 0; i < 10; i++) + { + sb.AppendLine(str); + } + + // Strict comparer, because the image is sparse: + ImageComparer comparer = ImageComparer.TolerantPercentage(0.0001F); + + provider.VerifyOperation( + comparer, + img => img.Mutate(c => c.ProcessWithCanvas(canvas => canvas.DrawText( + CreateTextOptionsAt(font, new PointF(10, 1)), + sb.ToString(), + Brushes.Solid(Color.Black), + pen: null))), + false, + false); + } + + [Theory] + [WithSolidFilledImages(400, 550, "White", PixelTypes.Rgba32, 1, 5, true)] + [WithSolidFilledImages(400, 550, "White", PixelTypes.Rgba32, 1.5, 3, true)] + [WithSolidFilledImages(400, 550, "White", PixelTypes.Rgba32, 2, 2, true)] + [WithSolidFilledImages(400, 100, "White", PixelTypes.Rgba32, 1, 5, false)] + [WithSolidFilledImages(400, 100, "White", PixelTypes.Rgba32, 1.5, 3, false)] + [WithSolidFilledImages(400, 100, "White", PixelTypes.Rgba32, 2, 2, false)] + public void FontShapesAreRenderedCorrectly_WithLineSpacing( + TestImageProvider provider, + float lineSpacing, + int lineCount, + bool wrap) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 16); + + StringBuilder sb = new(); + string str = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna."; + + for (int i = 0; i < lineCount; i++) + { + sb.AppendLine(str); + } + + RichTextOptions textOptions = new(font) + { + KerningMode = KerningMode.Standard, + VerticalAlignment = VerticalAlignment.Top, + HorizontalAlignment = HorizontalAlignment.Left, + LineSpacing = lineSpacing, + Origin = new PointF(10, 1) + }; + + if (wrap) + { + textOptions.WrappingLength = 300; + } + + Color color = Color.Black; + + // NET472 is 0.0045 different. + ImageComparer comparer = ImageComparer.TolerantPercentage(0.0046F); + + provider.VerifyOperation( + comparer, + img => img.Mutate(c => c.ProcessWithCanvas(canvas => canvas.DrawText( + textOptions, + sb.ToString(), + Brushes.Solid(color), + pen: null))), + $"linespacing_{lineSpacing}_linecount_{lineCount}_wrap_{wrap}", + false, + false); + } + + [Theory] + [WithSolidFilledImages(200, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.SixLaborsSampleAB, AB)] + [WithSolidFilledImages(900, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.OpenSans, TestText)] + [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 50, TestFonts.OpenSans, TestText)] + public void FontShapesAreRenderedCorrectlyWithAPen( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(fontName, fontSize); + Color color = Color.Black; + + provider.VerifyOperation( + OutlinedTextDrawingComparer, + img => img.Mutate(c => c.ProcessWithCanvas(canvas => canvas.DrawText( + CreateTextOptionsAt(new Font(font, fontSize), new PointF(x, y)), + text, + brush: null, + pen: Pens.Solid(color, 1)))), + $"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(200, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.SixLaborsSampleAB, AB)] + [WithSolidFilledImages(900, 150, "White", PixelTypes.Rgba32, 50, 0, 0, TestFonts.OpenSans, TestText)] + [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 50, TestFonts.OpenSans, TestText)] + public void FontShapesAreRenderedCorrectlyWithAPenPatterned( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(fontName, fontSize); + Color color = Color.Black; + + provider.VerifyOperation( + OutlinedTextDrawingComparer, + img => img.Mutate(c => c.ProcessWithCanvas(canvas => canvas.DrawText( + CreateTextOptionsAt(new Font(font, fontSize), new PointF(x, y)), + text, + brush: null, + pen: Pens.DashDot(color, 3)))), + $"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, TestFonts.OpenSans)] + public void TextPositioningIsRobust(TestImageProvider provider, string fontName) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(fontName, 30); + + string text = Repeat( + "Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!\n", + 20); + + RichTextOptions textOptions = new(font) + { + WrappingLength = 1000, + Origin = new PointF(10, 50) + }; + + string details = fontName.Replace(" ", string.Empty); + + // Based on the reported 0.1755% difference with AccuracyMultiple = 8 + // We should avoid quality regressions leading to higher difference! + ImageComparer comparer = ImageComparer.TolerantPercentage(0.2f); + + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, Brushes.Solid(Color.Black), pen: null)), + details, + comparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + + [Fact] + public void CanDrawTextWithEmptyPath() + { + // The following font/text combination generates an empty path. + Font font = CreateFont(TestFonts.WendyOne, 72); + const string text = "Hello\0World"; + RichTextOptions textOptions = new(font); + FontRectangle textSize = TextMeasurer.MeasureSize(text, textOptions); + + Assert.NotEqual(FontRectangle.Empty, textSize); + + using Image image = new(Configuration.Default, (int)textSize.Width + 20, (int)textSize.Height + 20); + image.Mutate(x => x.ProcessWithCanvas(canvas => canvas.DrawText( + CreateTextOptionsAt(font, Vector2.Zero), + text, + Brushes.Solid(Color.Black), + pen: null))); + } + + [Theory] + [WithSolidFilledImages(300, 200, nameof(Color.White), PixelTypes.Rgba32, TestFonts.OpenSans, 32, 75F)] + [WithSolidFilledImages(300, 200, nameof(Color.White), PixelTypes.Rgba32, TestFonts.OpenSans, 40, 90F)] + public void CanRotateFilledFont_Issue175( + TestImageProvider provider, + string fontName, + int fontSize, + float angle) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(fontName, fontSize); + const string text = "QuickTYZ"; + AffineTransformBuilder builder = new AffineTransformBuilder().AppendRotationDegrees(angle); + + RichTextOptions textOptions = new(font); + FontRectangle renderable = TextMeasurer.MeasureRenderableBounds(text, textOptions); + Matrix4x4 transform = new(builder.BuildMatrix(Rectangle.Round(new RectangleF(renderable.X, renderable.Y, renderable.Width, renderable.Height)))); + + DrawingOptions drawingOptions = new() { Transform = transform }; + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(drawingOptions, canvas => canvas.DrawText( + textOptions, + text, + Brushes.Solid(Color.Black), + pen: null)), + $"F({fontName})-S({fontSize})-A({angle})-{ToTestOutputDisplayText(text)})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(300, 200, nameof(Color.White), PixelTypes.Rgba32, TestFonts.OpenSans, 32, 75F, 1)] + [WithSolidFilledImages(300, 200, nameof(Color.White), PixelTypes.Rgba32, TestFonts.OpenSans, 40, 90F, 2)] + public void CanRotateOutlineFont_Issue175( + TestImageProvider provider, + string fontName, + int fontSize, + float angle, + int strokeWidth) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(fontName, fontSize); + const string text = "QuickTYZ"; + AffineTransformBuilder builder = new AffineTransformBuilder().AppendRotationDegrees(angle); + + RichTextOptions textOptions = new(font); + FontRectangle renderable = TextMeasurer.MeasureRenderableBounds(text, textOptions); + Matrix4x4 transform = new(builder.BuildMatrix(Rectangle.Round(new RectangleF(renderable.X, renderable.Y, renderable.Width, renderable.Height)))); + + DrawingOptions drawingOptions = new() { Transform = transform }; + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(drawingOptions, canvas => canvas.DrawText( + textOptions, + text, + brush: null, + pen: Pens.Solid(Color.Black, strokeWidth))), + $"F({fontName})-S({fontSize})-A({angle})-STR({strokeWidth})-{ToTestOutputDisplayText(text)})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(500, 200, nameof(Color.Black), PixelTypes.Rgba32, 32)] + [WithSolidFilledImages(500, 300, nameof(Color.Black), PixelTypes.Rgba32, 40)] + public void DrawRichText( + TestImageProvider provider, + int fontSize) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, fontSize); + Font font2 = CreateFont(TestFonts.OpenSans, fontSize * 1.5f); + const string text = "The quick brown fox jumps over the lazy dog"; + + RichTextOptions textOptions = new(font) + { + Origin = new Vector2(15), + WrappingLength = 400, + TextRuns = + [ + new RichTextRun + { + Start = 0, + End = 3, + OverlinePen = Pens.Solid(Color.Yellow, 1), + StrikeoutPen = Pens.Solid(Color.HotPink, 5), + }, + + new RichTextRun + { + Start = 4, + End = 10, + TextDecorations = TextDecorations.Strikeout, + StrikeoutPen = Pens.Solid(Color.Red), + OverlinePen = Pens.Solid(Color.Green, 9), + Brush = Brushes.Solid(Color.Red), + }, + + new RichTextRun + { + Start = 10, + End = 13, + Font = font2, + TextDecorations = TextDecorations.Strikeout, + StrikeoutPen = Pens.Solid(Color.White, 6), + OverlinePen = Pens.Solid(Color.Orange, 2), + }, + + new RichTextRun + { + Start = 19, + End = 23, + TextDecorations = TextDecorations.Underline, + UnderlinePen = Pens.Dot(Color.Fuchsia, 5), + Brush = Brushes.Solid(Color.Blue), + }, + + new RichTextRun + { + Start = 23, + End = 25, + TextDecorations = TextDecorations.Underline, + UnderlinePen = Pens.Solid(Color.White), + } + ] + }; + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, Brushes.Solid(Color.White), pen: null)), + $"RichText-F({fontSize})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(500, 200, nameof(Color.Black), PixelTypes.Rgba32, 32)] + [WithSolidFilledImages(500, 300, nameof(Color.Black), PixelTypes.Rgba32, 40)] + public void DrawRichTextArabic( + TestImageProvider provider, + int fontSize) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.MeQuranVolyNewmet, fontSize); + string text = "بِسْمِ ٱللَّهِ ٱلرَّحْمَٟنِ ٱلرَّحِيمِ"; + + RichTextOptions textOptions = new(font) + { + Origin = new Vector2(15), + WrappingLength = 400, + TextRuns = + [ + new RichTextRun { Start = 0, End = CodePoint.GetCodePointCount(text.AsSpan()), TextDecorations = TextDecorations.Underline } + ] + }; + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, Brushes.Solid(Color.White), pen: null)), + $"RichText-Arabic-F({fontSize})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(500, 200, nameof(Color.Black), PixelTypes.Rgba32, 32)] + [WithSolidFilledImages(500, 300, nameof(Color.Black), PixelTypes.Rgba32, 40)] + public void DrawRichTextRainbow( + TestImageProvider provider, + int fontSize) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, fontSize); + const string text = "The quick brown fox jumps over the lazy dog"; + + SolidPen[] colors = + [ + new SolidPen(Color.Red), + new SolidPen(Color.Orange), + new SolidPen(Color.Yellow), + new SolidPen(Color.Green), + new SolidPen(Color.Blue), + new SolidPen(Color.Indigo), + new SolidPen(Color.Violet) + ]; + + List runs = []; + for (int i = 0; i < text.Length; i++) + { + SolidPen pen = colors[i % colors.Length]; + runs.Add(new RichTextRun + { + Start = i, + End = i + 1, + UnderlinePen = pen + }); + } + + RichTextOptions textOptions = new(font) + { + Origin = new Vector2(15), + WrappingLength = 400, + TextRuns = runs, + }; + + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, Brushes.Solid(Color.White), pen: null)), + $"RichText-Rainbow-F({fontSize})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(100, 100, nameof(Color.Black), PixelTypes.Rgba32, "M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50", "spiral")] + [WithSolidFilledImages(350, 350, nameof(Color.Black), PixelTypes.Rgba32, "M275 175 A100 100 0 1 1 275 174", "circle")] + [WithSolidFilledImages(120, 120, nameof(Color.Black), PixelTypes.Rgba32, "M50,10 L 90 90 L 10 90 L50 10", "triangle")] + public void CanDrawRichTextAlongPathHorizontal(TestImageProvider provider, string svgPath, string exampleImageKey) + where TPixel : unmanaged, IPixel + { + bool parsed = Path.TryParseSvgPath(svgPath, out IPath path); + Assert.True(parsed); + + Font font = CreateFont(TestFonts.OpenSans, 13); + + const string text = "Quick brown fox jumps over the lazy dog."; + RichTextRun run = new() + { + Start = 0, + End = text.GetGraphemeCount(), + StrikeoutPen = new SolidPen(Color.Red) + }; + + RichTextOptions textOptions = new(font) + { + WrappingLength = path.ComputeLength(), + VerticalAlignment = VerticalAlignment.Bottom, + HorizontalAlignment = HorizontalAlignment.Left, + Path = path, + TextRuns = [run] + }; + + provider.RunValidatingProcessorTest( + x => x.ProcessWithCanvas(canvas => canvas.DrawText(textOptions, text, Brushes.Solid(Color.White), pen: null)), + $"RichText-Path-({exampleImageKey})", + TextDrawingComparer, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithBlankImage(100, 100, PixelTypes.Rgba32, "M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50", "spiral")] + [WithBlankImage(350, 350, PixelTypes.Rgba32, "M275 175 A100 100 0 1 1 275 174", "circle")] + [WithBlankImage(120, 120, PixelTypes.Rgba32, "M50,10 L 90 90 L 10 90 L50 10", "triangle")] + public void CanDrawTextAlongPathHorizontal(TestImageProvider provider, string svgPath, string exampleImageKey) + where TPixel : unmanaged, IPixel + { + bool parsed = Path.TryParseSvgPath(svgPath, out IPath path); + Assert.True(parsed); + + const string text = "Quick brown fox jumps over the lazy dog."; + + Font font = CreateFont(TestFonts.OpenSans, 13); + RichTextOptions textOptions = new(font) + { + WrappingLength = path.ComputeLength(), + VerticalAlignment = VerticalAlignment.Bottom, + HorizontalAlignment = HorizontalAlignment.Left, + TextRuns = [new RichTextRun { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Strikeout }], + }; + + IPathCollection glyphs = TextBuilder.GeneratePaths(text, path, textOptions); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.Draw(Pens.Solid(Color.Red, 1), path); + canvas.Fill(Brushes.Solid(Color.Black), glyphs); + }), + new { type = exampleImageKey }, + comparer: ImageComparer.TolerantPercentage(0.0025f)); + } + + [Theory] + [WithBlankImage(350, 350, PixelTypes.Rgba32, "M225 175 A50 50 0 1 1 225 174", "circle")] + [WithBlankImage(250, 250, PixelTypes.Rgba32, "M100,60 L 140 140 L 60 140 L100 60", "triangle")] + public void CanDrawTextAlongPathVertical(TestImageProvider provider, string svgPath, string exampleImageKey) + where TPixel : unmanaged, IPixel + { + bool parsed = Path.TryParseSvgPath(svgPath, out IPath path); + Assert.True(parsed); + + Font font = CreateFont(TestFonts.OpenSans, 13); + RichTextOptions textOptions = new(font) + { + WrappingLength = path.ComputeLength() / 4, + VerticalAlignment = VerticalAlignment.Bottom, + HorizontalAlignment = HorizontalAlignment.Left, + LayoutMode = LayoutMode.VerticalLeftRight + }; + + const string text = "Quick brown fox jumps over the lazy dog."; + IPathCollection glyphs = TextBuilder.GeneratePaths(text, path, textOptions); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.Draw(Pens.Solid(Color.Red, 1), path); + canvas.Fill(Brushes.Solid(Color.Black), glyphs); + }), + new { type = exampleImageKey }, + comparer: ImageComparer.TolerantPercentage(0.002f)); + } + + [Theory] + [WithSolidFilledImages(1000, 1000, "White", PixelTypes.Rgba32)] + public void PathAndTextDrawingMatch(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // https://github.com/SixLabors/ImageSharp.Drawing/issues/234 + Font font = CreateFont(TestFonts.NettoOffc, 300); + const string text = "all"; + + provider.VerifyOperation( + TextDrawingComparer, + img => + { + foreach (HorizontalAlignment ha in (HorizontalAlignment[])Enum.GetValues(typeof(HorizontalAlignment))) + { + foreach (VerticalAlignment va in (VerticalAlignment[])Enum.GetValues(typeof(VerticalAlignment))) + { + TextOptions to = new(font) + { + HorizontalAlignment = ha, + VerticalAlignment = va, + }; + + FontRectangle bounds = TextMeasurer.MeasureBounds(text, to); + float x = (img.Size.Width - bounds.Width) / 2; + PointF[] pathLine = + [ + new PointF(x, 500), + new PointF(x + bounds.Width, 500) + ]; + + IPath path = new PathBuilder().AddLine(pathLine[0], pathLine[1]).Build(); + + RichTextOptions rto = new(font) + { + Origin = pathLine[0], + HorizontalAlignment = ha, + VerticalAlignment = va, + }; + + IPathCollection tb = TextBuilder.GeneratePaths(text, path, to); + + img.Mutate(i => i.ProcessWithCanvas(canvas => + { + canvas.DrawLine(new SolidPen(Color.Red, 30), pathLine); + canvas.DrawText(rto, text, Brushes.Solid(Color.Black), pen: null); + canvas.Fill(Brushes.ForwardDiagonal(Color.HotPink), tb); + })); + } + } + }); + } + + [Theory] + [WithBlankImage(500, 400, PixelTypes.Rgba32)] + public void CanFillTextVertical(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 36); + Font fallback = CreateFont(TestFonts.NotoSansKRRegular, 36); + + const string text = "한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo"; + RichTextOptions textOptions = new(font) + { + Origin = new Vector2(0, 0), + FallbackFontFamilies = [fallback.Family], + WrappingLength = 300, + LayoutMode = LayoutMode.VerticalLeftRight, + TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline } + ] + }; + + IReadOnlyList glyphs = TextBuilder.GenerateGlyphs(text, textOptions); + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.DrawGlyphs(Brushes.Solid(Color.Black), Pens.Solid(Color.Black, 1F), glyphs); + }), + comparer: ImageComparer.TolerantPercentage(0.002f)); + } + + [Theory] + [WithBlankImage(500, 400, PixelTypes.Rgba32)] + public void CanFillTextVerticalMixed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 36); + Font fallback = CreateFont(TestFonts.NotoSansKRRegular, 36); + + const string text = "한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo"; + RichTextOptions textOptions = new(font) + { + FallbackFontFamilies = [fallback.Family], + WrappingLength = 400, + LayoutMode = LayoutMode.VerticalMixedLeftRight, + TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline } + ] + }; + + IPathCollection glyphs = TextBuilder.GeneratePaths(text, textOptions); + + DrawingOptions options = new() { ShapeOptions = new ShapeOptions { IntersectionRule = IntersectionRule.NonZero } }; + + provider.RunValidatingProcessorTest( + c => + { + c.ProcessWithCanvas(canvas => canvas.Fill(Brushes.Solid(Color.White))); + c.ProcessWithCanvas(options, canvas => canvas.Fill(Brushes.Solid(Color.Black), glyphs)); + }, + comparer: ImageComparer.TolerantPercentage(0.002f)); + } + + [Theory] + [WithBlankImage(500, 400, PixelTypes.Rgba32)] + public void CanDrawTextVertical(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 36); + Font fallback = CreateFont(TestFonts.NotoSansKRRegular, 36); + + const string text = "한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo"; + RichTextOptions textOptions = new(font) + { + FallbackFontFamilies = [fallback.Family], + WrappingLength = 400, + LayoutMode = LayoutMode.VerticalLeftRight, + LineSpacing = 1.4F, + TextRuns = [ + new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline } + ] + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.DrawText(textOptions, text, Brushes.Solid(Color.Black), pen: null); + }), + comparer: ImageComparer.TolerantPercentage(0.002f)); + } + + [Theory] + [WithBlankImage(48, 935, PixelTypes.Rgba32)] + public void CanDrawTextVertical2(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (SystemFonts.TryGet("Yu Gothic", out FontFamily fontFamily)) + { + Font font = fontFamily.CreateFont(30F); + const string text = "あいうえお、「こんにちはー」。もしもし。ABCDEFG 日本語"; + RichTextOptions textOptions = new(font) + { + LayoutMode = LayoutMode.VerticalLeftRight, + LineSpacing = 1.4F, + TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline }] + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.DrawText(textOptions, text, Brushes.Solid(Color.Black), pen: null); + }), + comparer: ImageComparer.TolerantPercentage(0.002f)); + } + } + + [Theory] + [WithBlankImage(500, 400, PixelTypes.Rgba32)] + public void CanDrawTextVerticalMixed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Font font = CreateFont(TestFonts.OpenSans, 36); + Font fallback = CreateFont(TestFonts.NotoSansKRRegular, 36); + + const string text = "한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo 한국어 hangugeo"; + RichTextOptions textOptions = new(font) + { + FallbackFontFamilies = [fallback.Family], + WrappingLength = 400, + LayoutMode = LayoutMode.VerticalMixedLeftRight, + LineSpacing = 1.4F, + TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline }] + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.DrawText(textOptions, text, Brushes.Solid(Color.Black), pen: null); + }), + comparer: ImageComparer.TolerantPercentage(0.002f)); + } + + [Theory] + [WithBlankImage(48, 839, PixelTypes.Rgba32)] + public void CanDrawTextVerticalMixed2(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (SystemFonts.TryGet("Yu Gothic", out FontFamily fontFamily)) + { + Font font = fontFamily.CreateFont(30F); + const string text = "あいうえお、「こんにちはー」。もしもし。ABCDEFG 日本語"; + RichTextOptions textOptions = new(font) + { + LayoutMode = LayoutMode.VerticalMixedLeftRight, + LineSpacing = 1.4F, + TextRuns = [new RichTextRun() { Start = 0, End = text.GetGraphemeCount(), TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline } + ] + }; + + provider.RunValidatingProcessorTest( + c => c.ProcessWithCanvas(canvas => + { + canvas.Fill(Brushes.Solid(Color.White)); + canvas.DrawText(textOptions, text, Brushes.Solid(Color.Black), pen: null); + }), + comparer: ImageComparer.TolerantPercentage(0.002f)); + } + } + + [Theory] + [WithBlankImage(200, 200, PixelTypes.Rgba32)] + public void CanRenderTextOutOfBoundsIssue301(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + ImageComparer.TolerantPercentage(0.01f), + img => + { + Font font = CreateFont(TestFonts.OpenSans, 70); + + const string txt = "V"; + FontRectangle size = TextMeasurer.MeasureBounds(txt, new TextOptions(font)); + + img.Mutate(x => x.Resize((int)size.Width, (int)size.Height)); + + RichTextOptions options = new(font) + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Origin = new Vector2(size.Width / 2, size.Height / 2) + }; + + LinearGradientBrush brush = new( + new PointF(0, 0), + new PointF(20, 20), + GradientRepetitionMode.Repeat, + new ColorStop(0, Color.Red), + new ColorStop(0.5f, Color.Green), + new ColorStop(0.5f, Color.Yellow), + new ColorStop(1f, Color.Blue)); + + img.Mutate(m => m.ProcessWithCanvas(canvas => canvas.DrawText(options, txt, brush, pen: null))); + }, + false, + false); + + private static RichTextOptions CreateTextOptionsAt(Font font, PointF origin) + => new(font) { Origin = origin }; + + private static RichTextOptions CreateTextOptionsAt(Font font, Vector2 origin) + => new(font) { Origin = new PointF(origin.X, origin.Y) }; + + private static string Repeat(string str, int times) => string.Concat(Enumerable.Repeat(str, times)); + + private static string ToTestOutputDisplayText(string text) + { + string fnDisplayText = text.Replace("\n", string.Empty); + return fnDisplayText[..Math.Min(fnDisplayText.Length, 4)]; + } + + private static Font CreateFont(string fontName, float size) + => TestFontUtilities.GetFont(fontName, size); +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.cs new file mode 100644 index 000000000..87de3c0a5 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Processing/ProcessWithDrawingCanvasTests.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Drawing.Tests.Processing; + +[GroupOutput("Drawing")] +public partial class ProcessWithDrawingCanvasTests +{ +} diff --git a/tests/ImageSharp.Drawing.Tests/Processing/RasterizerDefaultsExtensionsTests.cs b/tests/ImageSharp.Drawing.Tests/Processing/RasterizerDefaultsExtensionsTests.cs index ffea506c6..8ff08a310 100644 --- a/tests/ImageSharp.Drawing.Tests/Processing/RasterizerDefaultsExtensionsTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Processing/RasterizerDefaultsExtensionsTests.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.Drawing.Processing.Backends; -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -11,18 +10,6 @@ namespace SixLabors.ImageSharp.Drawing.Tests.Processing; public class RasterizerDefaultsExtensionsTests { - [Fact] - public void GetDefaultRasterizerFromConfiguration_AlwaysReturnsDefaultInstance() - { - Configuration configuration = new(); - - IRasterizer first = configuration.GetRasterizer(); - IRasterizer second = configuration.GetRasterizer(); - - Assert.Same(first, second); - Assert.Same(DefaultRasterizer.Instance, first); - } - [Fact] public void GetDefaultDrawingBackendFromConfiguration_AlwaysReturnsDefaultInstance() { @@ -32,43 +19,7 @@ public void GetDefaultDrawingBackendFromConfiguration_AlwaysReturnsDefaultInstan IDrawingBackend second = configuration.GetDrawingBackend(); Assert.Same(first, second); - Assert.Same(CpuDrawingBackend.Instance, first); - } - - [Fact] - public void SetRasterizerOnConfiguration_RoundTrips() - { - Configuration configuration = new(); - RecordingRasterizer rasterizer = new(); - - configuration.SetRasterizer(rasterizer); - - Assert.Same(rasterizer, configuration.GetRasterizer()); - Assert.IsType(configuration.GetDrawingBackend()); - } - - [Fact] - public void SetRasterizerOnProcessingContext_RoundTrips() - { - Configuration configuration = new(); - FakeImageOperationsProvider.FakeImageOperations context = new(configuration, null, true); - RecordingRasterizer rasterizer = new(); - - context.SetRasterizer(rasterizer); - - Assert.Same(rasterizer, context.GetRasterizer()); - Assert.IsType(context.GetDrawingBackend()); - } - - [Fact] - public void GetRasterizerFromProcessingContext_FallsBackToConfiguration() - { - Configuration configuration = new(); - RecordingRasterizer rasterizer = new(); - configuration.SetRasterizer(rasterizer); - FakeImageOperationsProvider.FakeImageOperations context = new(configuration, null, true); - - Assert.Same(rasterizer, context.GetRasterizer()); + Assert.Same(DefaultDrawingBackend.Instance, first); } [Fact] @@ -94,39 +45,46 @@ public void SetDrawingBackendOnProcessingContext_RoundTrips() Assert.Same(backend, context.GetDrawingBackend()); } - private sealed class RecordingRasterizer : IRasterizer + private sealed class RecordingDrawingBackend : IDrawingBackend { - public void Rasterize( - IPath path, - in RasterizerOptions options, - MemoryAllocator allocator, - ref TState state, - RasterizerScanlineHandler scanlineHandler) - where TState : struct + public void FlushCompositions( + Configuration configuration, + ICanvasFrame target, + CompositionScene compositionScene) + where TPixel : unmanaged, IPixel { } - } - private sealed class RecordingDrawingBackend : IDrawingBackend - { - public void FillPath( + public bool TryReadRegion( + Configuration configuration, + ICanvasFrame target, + Rectangle sourceRectangle, + Buffer2D destination) + where TPixel : unmanaged, IPixel + => false; + + public void ComposeLayer( Configuration configuration, - ImageFrame source, - IPath path, - Brush brush, - in GraphicsOptions graphicsOptions, - in RasterizerOptions rasterizerOptions, - Rectangle brushBounds, - MemoryAllocator allocator) + ICanvasFrame source, + ICanvasFrame destination, + Point destinationOffset, + GraphicsOptions options) where TPixel : unmanaged, IPixel { } - public void RasterizeCoverage( - IPath path, - in RasterizerOptions rasterizerOptions, - MemoryAllocator allocator, - Buffer2D destination) + public ICanvasFrame CreateLayerFrame( + Configuration configuration, + ICanvasFrame parentTarget, + int width, + int height) + where TPixel : unmanaged, IPixel + => DefaultDrawingBackend.Instance.CreateLayerFrame(configuration, parentTarget, width, height); + + public void ReleaseFrameResources( + Configuration configuration, + ICanvasFrame target) + where TPixel : unmanaged, IPixel { } } diff --git a/tests/ImageSharp.Drawing.Tests/Rasterization/DefaultRasterizerRegressionTests.cs b/tests/ImageSharp.Drawing.Tests/Rasterization/DefaultRasterizerRegressionTests.cs new file mode 100644 index 000000000..3f3c9af54 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Rasterization/DefaultRasterizerRegressionTests.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + + +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing.Backends; + +namespace SixLabors.ImageSharp.Drawing.Tests.Rasterization; + +public class DefaultRasterizerRegressionTests +{ + [Fact] + public void EmitsCoverageForSubpixelThinRectangle() + { + RectangularPolygon path = new(0.3F, 0.2F, 0.7F, 1.423F); + RasterizerOptions options = new(new Rectangle(0, 0, 12, 20), IntersectionRule.EvenOdd, RasterizationMode.Antialiased, RasterizerSamplingOrigin.PixelBoundary, 0.5f); + float[] coverage = new float[options.Interest.Width * options.Interest.Height]; + int width = options.Interest.Width; + int top = options.Interest.Top; + int dirtyRows = 0; + float maxCoverage = 0F; + + DefaultRasterizer.RasterizeRows(path, options, Configuration.Default.MemoryAllocator, CaptureRow); + + Assert.True(dirtyRows > 0); + Assert.True(maxCoverage > 0F); + + void CaptureRow(int y, int startX, Span rowCoverage) + { + int row = y - top; + rowCoverage.CopyTo(coverage.AsSpan((row * width) + startX, rowCoverage.Length)); + dirtyRows++; + + for (int i = 0; i < rowCoverage.Length; i++) + { + if (rowCoverage[i] > maxCoverage) + { + maxCoverage = rowCoverage[i]; + } + } + } + } + + [Fact] + public void RasterizesFractionalRectangleCoverageDeterministically() + { + RectangularPolygon path = new(0.25F, 0.25F, 1F, 1F); + RasterizerOptions options = new(new Rectangle(0, 0, 2, 2), IntersectionRule.NonZero, RasterizationMode.Antialiased, RasterizerSamplingOrigin.PixelBoundary, 0.5f); + + float[] coverage = Rasterize(path, options); + float[] expected = + [ + 0.5625F, 0.1875F, + 0.1875F, 0.0625F + ]; + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], coverage[i], 3); + } + } + + [Fact] + public void AliasedMode_EmitsBinaryCoverage() + { + RectangularPolygon path = new(0.25F, 0.25F, 1F, 1F); + RasterizerOptions options = new(new Rectangle(0, 0, 2, 2), IntersectionRule.NonZero, RasterizationMode.Aliased, RasterizerSamplingOrigin.PixelBoundary, 0.5f); + + float[] coverage = Rasterize(path, options); + float[] expected = + [ + 1F, 0F, + 0F, 0F + ]; + + Assert.Equal(expected, coverage); + } + + [Fact] + public void ThrowsForInterestTooWideForCoverStrideMath() + { + RectangularPolygon path = new(0F, 0F, 1F, 1F); + RasterizerOptions options = new(new Rectangle(0, 0, (int.MaxValue / 2) + 1, 1), IntersectionRule.NonZero, RasterizationMode.Antialiased, RasterizerSamplingOrigin.PixelBoundary, 0.5f); + + void Rasterize() => + DefaultRasterizer.RasterizeRows( + path, + options, + Configuration.Default.MemoryAllocator, + static (int y, int startX, Span coverage) => { }); + + ImageProcessingException exception = Assert.Throws(Rasterize); + Assert.Contains("too large", exception.Message); + } + + private static float[] Rasterize(IPath path, in RasterizerOptions options) + { + int width = options.Interest.Width; + int height = options.Interest.Height; + float[] coverage = new float[width * height]; + int top = options.Interest.Top; + DefaultRasterizer.RasterizeRows(path, options, Configuration.Default.MemoryAllocator, CaptureRow); + return coverage; + + void CaptureRow(int y, int startX, Span rowCoverage) + { + int row = y - top; + rowCoverage.CopyTo(coverage.AsSpan((row * width) + startX, rowCoverage.Length)); + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Rasterization/DefaultRasterizerTests.cs b/tests/ImageSharp.Drawing.Tests/Rasterization/DefaultRasterizerTests.cs new file mode 100644 index 000000000..858301d64 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Rasterization/DefaultRasterizerTests.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Drawing.Processing.Backends; + +namespace SixLabors.ImageSharp.Drawing.Tests.Rasterization; + +public class DefaultRasterizerTests +{ + [Theory] + [InlineData(IntersectionRule.EvenOdd)] + [InlineData(IntersectionRule.NonZero)] + public void MatchesDefaultRasterizer_ForLargeSelfIntersectingPath(IntersectionRule rule) + { + IPath path = PolygonFactory.CreatePolygon( + (1, 4), + (1, 3), + (3, 3), + (3, 2), + (2, 2), + (2, 4), + (1, 4), + (1, 1), + (4, 1), + (4, 4), + (3, 4), + (3, 5), + (2, 5), + (2, 4), + (1, 4)) + .Transform(Matrix4x4.CreateScale(200F)); + + Rectangle interest = Rectangle.Ceiling(path.Bounds); + RasterizerOptions options = new(interest, rule, RasterizationMode.Antialiased, RasterizerSamplingOrigin.PixelBoundary, 0.5f); + + float[] expected = RasterizeSequential(path, options); + float[] actual = Rasterize(path, options); + + AssertCoverageEqual(expected, actual); + } + + [Fact] + public void MatchesDefaultRasterizer_ForPixelCenterSampling() + { + RectangularPolygon path = new(20.2F, 30.4F, 700.1F, 540.6F); + Rectangle interest = Rectangle.Ceiling(path.Bounds); + RasterizerOptions options = new( + interest, + IntersectionRule.NonZero, + RasterizationMode.Antialiased, + RasterizerSamplingOrigin.PixelCenter, + 0.5f); + + float[] expected = RasterizeSequential(path, options); + float[] actual = Rasterize(path, options); + + AssertCoverageEqual(expected, actual); + } + + private static float[] Rasterize(IPath path, in RasterizerOptions options) + { + int width = options.Interest.Width; + int height = options.Interest.Height; + float[] coverage = new float[width * height]; + int top = options.Interest.Top; + DefaultRasterizer.RasterizeRows(path, options, Configuration.Default.MemoryAllocator, CaptureRow); + + return coverage; + + void CaptureRow(int y, int startX, Span rowCoverage) + { + int row = y - top; + rowCoverage.CopyTo(coverage.AsSpan((row * width) + startX, rowCoverage.Length)); + } + } + + private static float[] RasterizeSequential(IPath path, in RasterizerOptions options) + { + int width = options.Interest.Width; + int height = options.Interest.Height; + float[] coverage = new float[width * height]; + int top = options.Interest.Top; + DefaultRasterizer.RasterizeRowsSequential(path, options, Configuration.Default.MemoryAllocator, CaptureRow); + + return coverage; + + void CaptureRow(int y, int startX, Span rowCoverage) + { + int row = y - top; + rowCoverage.CopyTo(coverage.AsSpan((row * width) + startX, rowCoverage.Length)); + } + } + + private static void AssertCoverageEqual(ReadOnlySpan expected, ReadOnlySpan actual) + { + Assert.Equal(expected.Length, actual.Length); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i], 6); + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/IntersectionsGenerator.py b/tests/ImageSharp.Drawing.Tests/Rasterization/IntersectionsGenerator.py similarity index 100% rename from tests/ImageSharp.Drawing.Tests/Shapes/Scan/IntersectionsGenerator.py rename to tests/ImageSharp.Drawing.Tests/Rasterization/IntersectionsGenerator.py diff --git a/tests/ImageSharp.Drawing.Tests/Rasterization/NumericCornerCases.jpg b/tests/ImageSharp.Drawing.Tests/Rasterization/NumericCornerCases.jpg new file mode 100644 index 000000000..4d47bc457 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Rasterization/NumericCornerCases.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14120aec3ece697f2e69559b542c59fb4008083d1e9300e9174700223551645e +size 623151 diff --git a/tests/ImageSharp.Drawing.Tests/Rasterization/SimplePolygon_AllEmitCases.png b/tests/ImageSharp.Drawing.Tests/Rasterization/SimplePolygon_AllEmitCases.png new file mode 100644 index 000000000..fb6d09308 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/Rasterization/SimplePolygon_AllEmitCases.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99b523663db7a64a03a836719b54cf64ded5a1128317a95282e866cb9f368ab4 +size 28411 diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathTests.cs index 3ed572163..846c14de0 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Shapes/InternalPathTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; + namespace SixLabors.ImageSharp.Drawing.Tests.Shapes; /// diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/PathBuilderTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/PathBuilderTests.cs index 1968ec8af..a292c0d08 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/PathBuilderTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Shapes/PathBuilderTests.cs @@ -164,7 +164,7 @@ public void DefaultTransform() Vector2 point1 = new(10, 10); Vector2 point2 = new(10, 90); Vector2 point3 = new(50, 50); - Matrix3x2 matrix = Matrix3x2.CreateTranslation(new Vector2(5, 5)); + Matrix4x4 matrix = Matrix4x4.CreateTranslation(5, 5, 0); PathBuilder builder = new(matrix); builder.AddLines(point1, point2, point3); @@ -178,7 +178,7 @@ public void SetTransform() Vector2 point1 = new(10, 10); Vector2 point2 = new(10, 90); Vector2 point3 = new(50, 50); - Matrix3x2 matrix = Matrix3x2.CreateTranslation(new Vector2(100, 100)); + Matrix4x4 matrix = Matrix4x4.CreateTranslation(100, 100, 0); PathBuilder builder = new(); builder.AddLines(point1, point2, point3); @@ -202,7 +202,7 @@ public void SetOriginLeaveMatrix() Vector2 point2 = new(10, 90); Vector2 point3 = new(50, 50); Vector2 origin = new(-50, -100); - PathBuilder builder = new(Matrix3x2.CreateScale(10)); + PathBuilder builder = new(Matrix4x4.CreateScale(10)); builder.AddLines(point1, point2, point3); diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/PathExtentionTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/PathExtentionTests.cs index 77314de9e..688a0a3e7 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/PathExtentionTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Shapes/PathExtentionTests.cs @@ -23,18 +23,18 @@ public void RotateInRadians() { const float Angle = (float)Math.PI; - this.mockPath.Setup(x => x.Transform(It.IsAny())) - .Callback(m => + this.mockPath.Setup(x => x.Transform(It.IsAny())) + .Callback(m => { // validate matrix in here - Matrix3x2 targetMatrix = Matrix3x2.CreateRotation(Angle, RectangleF.Center(this.bounds)); + Matrix4x4 targetMatrix = new(Matrix3x2.CreateRotation(Angle, RectangleF.Center(this.bounds))); Assert.Equal(targetMatrix, m); }).Returns(this.mockPath.Object); this.mockPath.Object.Rotate(Angle); - this.mockPath.Verify(x => x.Transform(It.IsAny()), Times.Once); + this.mockPath.Verify(x => x.Transform(It.IsAny()), Times.Once); } [Fact] @@ -42,20 +42,20 @@ public void RotateInDegrees() { const float Angle = 90; - this.mockPath.Setup(x => x.Transform(It.IsAny())) - .Callback(m => + this.mockPath.Setup(x => x.Transform(It.IsAny())) + .Callback(m => { // validate matrix in here const float Radians = (float)(Math.PI * Angle / 180.0); - Matrix3x2 targetMatrix = Matrix3x2.CreateRotation(Radians, RectangleF.Center(this.bounds)); + Matrix4x4 targetMatrix = new(Matrix3x2.CreateRotation(Radians, RectangleF.Center(this.bounds))); Assert.Equal(targetMatrix, m); }).Returns(this.mockPath.Object); this.mockPath.Object.RotateDegree(Angle); - this.mockPath.Verify(x => x.Transform(It.IsAny()), Times.Once); + this.mockPath.Verify(x => x.Transform(It.IsAny()), Times.Once); } [Fact] @@ -63,18 +63,18 @@ public void TranslateVector() { Vector2 point = new(98, 120); - this.mockPath.Setup(x => x.Transform(It.IsAny())) - .Callback(m => + this.mockPath.Setup(x => x.Transform(It.IsAny())) + .Callback(m => { // validate matrix in here - Matrix3x2 targetMatrix = Matrix3x2.CreateTranslation(point); + Matrix4x4 targetMatrix = Matrix4x4.CreateTranslation(point.X, point.Y, 0); Assert.Equal(targetMatrix, m); }).Returns(this.mockPath.Object); this.mockPath.Object.Translate(point); - this.mockPath.Verify(x => x.Transform(It.IsAny()), Times.Once); + this.mockPath.Verify(x => x.Transform(It.IsAny()), Times.Once); } [Fact] @@ -83,17 +83,17 @@ public void TranslateXY() const float X = 76; const float Y = 7; - this.mockPath.Setup(p => p.Transform(It.IsAny())) - .Callback(m => + this.mockPath.Setup(p => p.Transform(It.IsAny())) + .Callback(m => { // validate matrix in here - Matrix3x2 targetMatrix = Matrix3x2.CreateTranslation(new Vector2(X, Y)); + Matrix4x4 targetMatrix = Matrix4x4.CreateTranslation(X, Y, 0); Assert.Equal(targetMatrix, m); }).Returns(this.mockPath.Object); this.mockPath.Object.Translate(X, Y); - this.mockPath.Verify(p => p.Transform(It.IsAny()), Times.Once); + this.mockPath.Verify(p => p.Transform(It.IsAny()), Times.Once); } } diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/PolygonClipper/ClipperTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/PolygonClipper/ClipperTests.cs deleted file mode 100644 index 7943ac2df..000000000 --- a/tests/ImageSharp.Drawing.Tests/Shapes/PolygonClipper/ClipperTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Shapes.PolygonGeometry; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Drawing.Tests.PolygonClipper; - -public class ClipperTests -{ - private readonly RectangularPolygon bigSquare = new(10, 10, 40, 40); - private readonly RectangularPolygon hole = new(20, 20, 10, 10); - private readonly RectangularPolygon topLeft = new(0, 0, 20, 20); - private readonly RectangularPolygon topRight = new(30, 0, 20, 20); - private readonly RectangularPolygon topMiddle = new(20, 0, 10, 20); - - private readonly Polygon bigTriangle = new(new LinearLineSegment( - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300))); - - private readonly Polygon littleTriangle = new(new LinearLineSegment( - new Vector2(37, 85), - new Vector2(130, 40), - new Vector2(65, 137))); - - private static ComplexPolygon Clip(IPath shape, params IPath[] hole) - => ClippedShapeGenerator.GenerateClippedShapes(BooleanOperation.Difference, shape, hole); - - [Fact] - public void OverlappingTriangleCutRightSide() - { - Polygon triangle = new(new LinearLineSegment( - new Vector2(0, 50), - new Vector2(70, 0), - new Vector2(50, 100))); - - Polygon cutout = new(new LinearLineSegment( - new Vector2(20, 0), - new Vector2(70, 0), - new Vector2(70, 100), - new Vector2(20, 100))); - - ComplexPolygon shapes = Clip(triangle, cutout); - Assert.Single(shapes.Paths); - Assert.DoesNotContain(triangle, shapes.Paths); - } - - [Fact] - public void OverlappingTriangles() - { - ComplexPolygon shapes = Clip(this.bigTriangle, this.littleTriangle); - Assert.Single(shapes.Paths); - PointF[] path = shapes.Paths.Single().Flatten().First().Points.ToArray(); - - Assert.Equal(7, path.Length); - foreach (Vector2 p in this.bigTriangle.Flatten().First().Points.ToArray()) - { - Assert.Contains(p, path, new ApproximateFloatComparer(RectangularPolygonValueComparer.DefaultTolerance)); - } - } - - [Fact] - public void NonOverlapping() - { - IEnumerable shapes = Clip(this.topLeft, this.topRight).Paths - .OfType().Select(x => (RectangularPolygon)x); - - Assert.Single(shapes); - Assert.Contains( - shapes, x => RectangularPolygonValueComparer.Equals(this.topLeft, x)); - - Assert.DoesNotContain(this.topRight, shapes); - } - - [Fact] - public void OverLappingReturns1NewShape() - { - ComplexPolygon shapes = Clip(this.bigSquare, this.topLeft); - - Assert.Single(shapes.Paths); - Assert.DoesNotContain(shapes.Paths, x => RectangularPolygonValueComparer.Equals(this.bigSquare, x)); - Assert.DoesNotContain(shapes.Paths, x => RectangularPolygonValueComparer.Equals(this.topLeft, x)); - } - - [Fact] - public void OverlappingButNotCrossingReturnsOrigionalShapes() - { - IEnumerable shapes = Clip(this.bigSquare, this.hole).Paths - .OfType().Select(x => (RectangularPolygon)x); - - Assert.Equal(2, shapes.Count()); - - Assert.Contains(shapes, x => RectangularPolygonValueComparer.Equals(this.bigSquare, x)); - Assert.Contains(shapes, x => RectangularPolygonValueComparer.Equals(this.hole, x)); - } - - [Fact] - public void TouchingButNotOverlapping() - { - ComplexPolygon shapes = Clip(this.topMiddle, this.topLeft); - Assert.Single(shapes.Paths); - Assert.DoesNotContain(shapes.Paths, x => RectangularPolygonValueComparer.Equals(this.topMiddle, x)); - Assert.DoesNotContain(shapes.Paths, x => RectangularPolygonValueComparer.Equals(this.topLeft, x)); - } - - [Fact] - public void ClippingRectanglesCreateCorrectNumberOfPoints() - { - IEnumerable paths = new RectangularPolygon(10, 10, 40, 40) - .Clip(new RectangularPolygon(20, 0, 20, 20)) - .Flatten(); - - Assert.Single(paths); - PointF[] points = paths.First().Points.ToArray(); - - Assert.Equal(8, points.Length); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/PolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/PolygonTests.cs index 3af46a7a8..e1c4a5f89 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/PolygonTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Shapes/PolygonTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; + namespace SixLabors.ImageSharp.Drawing.Tests.Shapes; public class PolygonTests diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/RectangleTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/RectangleTests.cs index 9ea049e3e..aa6406b26 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/RectangleTests.cs +++ b/tests/ImageSharp.Drawing.Tests/Shapes/RectangleTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; +using SixLabors.ImageSharp.Drawing.Tests.TestUtilities; namespace SixLabors.ImageSharp.Drawing.Tests.Shapes; @@ -144,7 +145,7 @@ public void ShapePaths() public void TransformIdentityReturnsShapeObject() { IPath shape = new RectangularPolygon(0, 0, 200, 60); - IPath transformedShape = shape.Transform(Matrix3x2.Identity); + IPath transformedShape = shape.Transform(Matrix4x4.Identity); Assert.Same(shape, transformedShape); } @@ -154,7 +155,7 @@ public void Transform() { IPath shape = new RectangularPolygon(0, 0, 200, 60); - IPath newShape = shape.Transform(new Matrix3x2(0, 1, 1, 0, 20, 2)); + IPath newShape = shape.Transform(new Matrix4x4(new Matrix3x2(0, 1, 1, 0, 20, 2))); Assert.Equal(new PointF(20, 2), newShape.Bounds.Location); Assert.Equal(new SizeF(60, 200), newShape.Bounds.Size); diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/DefaultRasterizerTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/Scan/DefaultRasterizerTests.cs deleted file mode 100644 index 382d6fa86..000000000 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/DefaultRasterizerTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; - -namespace SixLabors.ImageSharp.Drawing.Tests.Shapes.Scan; - -public class DefaultRasterizerTests -{ - [Theory] - [InlineData(IntersectionRule.EvenOdd)] - [InlineData(IntersectionRule.NonZero)] - public void MatchesDefaultRasterizer_ForLargeSelfIntersectingPath(IntersectionRule rule) - { - IPath path = PolygonFactory.CreatePolygon( - (1, 4), - (1, 3), - (3, 3), - (3, 2), - (2, 2), - (2, 4), - (1, 4), - (1, 1), - (4, 1), - (4, 4), - (3, 4), - (3, 5), - (2, 5), - (2, 4), - (1, 4)) - .Transform(Matrix3x2.CreateScale(200F)); - - Rectangle interest = Rectangle.Ceiling(path.Bounds); - RasterizerOptions options = new(interest, rule); - - float[] expected = Rasterize(ScanlineRasterizer.Instance, path, options); - float[] actual = Rasterize(DefaultRasterizer.Instance, path, options); - - AssertCoverageEqual(expected, actual); - } - - [Fact] - public void MatchesDefaultRasterizer_ForPixelCenterSampling() - { - RectangularPolygon path = new(20.2F, 30.4F, 700.1F, 540.6F); - Rectangle interest = Rectangle.Ceiling(path.Bounds); - RasterizerOptions options = new( - interest, - IntersectionRule.NonZero, - samplingOrigin: RasterizerSamplingOrigin.PixelCenter); - - float[] expected = Rasterize(ScanlineRasterizer.Instance, path, options); - float[] actual = Rasterize(DefaultRasterizer.Instance, path, options); - - AssertCoverageEqual(expected, actual); - } - - private static float[] Rasterize(IRasterizer rasterizer, IPath path, in RasterizerOptions options) - { - int width = options.Interest.Width; - int height = options.Interest.Height; - float[] coverage = new float[width * height]; - CaptureState state = new(coverage, width, options.Interest.Top); - - rasterizer.Rasterize(path, options, Configuration.Default.MemoryAllocator, ref state, CaptureScanline); - - return coverage; - } - - private static void CaptureScanline(int y, Span scanline, ref CaptureState state) - { - int row = y - state.Top; - scanline.CopyTo(state.Coverage.AsSpan(row * state.Width, state.Width)); - } - - private static void AssertCoverageEqual(ReadOnlySpan expected, ReadOnlySpan actual) - { - Assert.Equal(expected.Length, actual.Length); - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i], 6); - } - } - - private readonly struct CaptureState - { - public CaptureState(float[] coverage, int width, int top) - { - this.Coverage = coverage; - this.Width = width; - this.Top = top; - } - - public float[] Coverage { get; } - - public int Width { get; } - - public int Top { get; } - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/NumericCornerCases.jpg b/tests/ImageSharp.Drawing.Tests/Shapes/Scan/NumericCornerCases.jpg deleted file mode 100644 index 91bdf70fd..000000000 Binary files a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/NumericCornerCases.jpg and /dev/null differ diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/SharpBlazeRasterizerTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/Scan/SharpBlazeRasterizerTests.cs deleted file mode 100644 index 19842362c..000000000 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/SharpBlazeRasterizerTests.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Shapes.Rasterization; - -namespace SixLabors.ImageSharp.Drawing.Tests.Shapes.Scan; - -public class SharpBlazeRasterizerTests -{ - [Fact] - public void EmitsCoverageForSubpixelThinRectangle() - { - RectangularPolygon path = new(0.3F, 0.2F, 0.7F, 1.423F); - RasterizerOptions options = new(new Rectangle(0, 0, 12, 20), IntersectionRule.EvenOdd); - CaptureState state = new(new float[options.Interest.Width * options.Interest.Height], options.Interest.Width, options.Interest.Top); - - DefaultRasterizer.Instance.Rasterize(path, options, Configuration.Default.MemoryAllocator, ref state, CaptureScanline); - - Assert.True(state.DirtyRows > 0); - Assert.True(state.MaxCoverage > 0F); - } - - [Fact] - public void RasterizesFractionalRectangleCoverageDeterministically() - { - RectangularPolygon path = new(0.25F, 0.25F, 1F, 1F); - RasterizerOptions options = new(new Rectangle(0, 0, 2, 2), IntersectionRule.NonZero); - - float[] coverage = Rasterize(DefaultRasterizer.Instance, path, options); - float[] expected = - [ - 0.5625F, 0.1875F, - 0.1875F, 0.0625F - ]; - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], coverage[i], 3); - } - } - - [Fact] - public void AliasedMode_EmitsBinaryCoverage() - { - RectangularPolygon path = new(0.25F, 0.25F, 1F, 1F); - RasterizerOptions options = new(new Rectangle(0, 0, 2, 2), IntersectionRule.NonZero, RasterizationMode.Aliased); - - float[] coverage = Rasterize(DefaultRasterizer.Instance, path, options); - float[] expected = - [ - 1F, 0F, - 0F, 0F - ]; - - Assert.Equal(expected, coverage); - } - - [Fact] - public void ThrowsForInterestTooWideForCoverStrideMath() - { - RectangularPolygon path = new(0F, 0F, 1F, 1F); - RasterizerOptions options = new(new Rectangle(0, 0, (int.MaxValue / 2) + 1, 1), IntersectionRule.NonZero); - NoopState state = default; - - void Rasterize() => - DefaultRasterizer.Instance.Rasterize( - path, - options, - Configuration.Default.MemoryAllocator, - ref state, - static (int y, Span scanline, ref NoopState localState) => { }); - - ImageProcessingException exception = Assert.Throws(Rasterize); - Assert.Contains("too large", exception.Message); - } - - private static float[] Rasterize(IRasterizer rasterizer, IPath path, in RasterizerOptions options) - { - int width = options.Interest.Width; - int height = options.Interest.Height; - float[] coverage = new float[width * height]; - CaptureState state = new(coverage, width, options.Interest.Top); - - rasterizer.Rasterize(path, options, Configuration.Default.MemoryAllocator, ref state, CaptureScanline); - return coverage; - } - - private static void CaptureScanline(int y, Span scanline, ref CaptureState state) - { - int row = y - state.Top; - scanline.CopyTo(state.Coverage.AsSpan(row * state.Width, state.Width)); - state.DirtyRows++; - - for (int i = 0; i < scanline.Length; i++) - { - if (scanline[i] > state.MaxCoverage) - { - state.MaxCoverage = scanline[i]; - } - } - } - - private struct CaptureState - { - public CaptureState(float[] coverage, int width, int top) - { - this.Coverage = coverage; - this.Width = width; - this.Top = top; - this.DirtyRows = 0; - this.MaxCoverage = 0F; - } - - public float[] Coverage { get; } - - public int Width { get; } - - public int Top { get; } - - public int DirtyRows { get; set; } - - public float MaxCoverage { get; set; } - } - - private struct NoopState - { - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/SimplePolygon_AllEmitCases.png b/tests/ImageSharp.Drawing.Tests/Shapes/Scan/SimplePolygon_AllEmitCases.png deleted file mode 100644 index 7b188191a..000000000 Binary files a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/SimplePolygon_AllEmitCases.png and /dev/null differ diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/TessellatedMultipolygonTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/Scan/TessellatedMultipolygonTests.cs deleted file mode 100644 index 6dae4fb1d..000000000 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/TessellatedMultipolygonTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Shapes; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Drawing.Tests.Shapes.Scan; - -public class TessellatedMultipolygonTests -{ - private static MemoryAllocator MemoryAllocator => Configuration.Default.MemoryAllocator; - - private static void VerifyRing(TessellatedMultipolygon.Ring ring, PointF[] originalPoints, bool originalPositive, bool isHole) - { - ReadOnlySpan points = ring.Vertices; - - Assert.Equal(originalPoints.Length + 1, points.Length); - Assert.Equal(points[0], points[points.Length - 1]); - Assert.Equal(originalPoints.Length, ring.VertexCount); - - originalPoints = originalPoints.CloneArray(); - - if ((originalPositive && isHole) || (!originalPositive && !isHole)) - { - originalPoints.AsSpan().Reverse(); - points = points.Slice(1); - } - else - { - points = points.Slice(0, points.Length - 1); - } - - Assert.True(originalPoints.AsSpan().SequenceEqual(points)); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Create_FromPolygon_Case1(bool reverseOriginal) - { - PointF[] points = PolygonFactory.CreatePointArray((0, 3), (3, 3), (3, 0), (1, 2), (1, 1), (0, 0)); - if (reverseOriginal) - { - points.AsSpan().Reverse(); - } - - Polygon polygon = new(points); - - using TessellatedMultipolygon multipolygon = TessellatedMultipolygon.Create(polygon, MemoryAllocator); - VerifyRing(multipolygon[0], points, reverseOriginal, false); - Assert.Equal(6, multipolygon.TotalVertexCount); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Create_FromPolygon_Case2(bool reverseOriginal) - { - PointF[] points = PolygonFactory.CreatePointArray((0, 0), (2, 0), (3, 1), (3, 0), (6, 0), (6, 2), (5, 2), (5, 1), (4, 1), (4, 2), (2, 2), (1, 1), (0, 2)); - if (reverseOriginal) - { - points.AsSpan().Reverse(); - } - - Polygon polygon = new(points); - - using TessellatedMultipolygon multipolygon = TessellatedMultipolygon.Create(polygon, MemoryAllocator); - - VerifyRing(multipolygon[0], points, !reverseOriginal, false); - } - - [Fact] - public void Create_FromRecangle() - { - RectangularPolygon polygon = new(10, 20, 100, 50); - - PointF[] points = polygon.Flatten().Single().Points.Span.ToArray(); - - using TessellatedMultipolygon multipolygon = TessellatedMultipolygon.Create(polygon, MemoryAllocator); - VerifyRing(multipolygon[0], points, true, false); - Assert.Equal(4, multipolygon.TotalVertexCount); - } - - [Fact] - public void Create_FromStar() - { - Star polygon = new(100, 100, 5, 30, 60); - PointF[] points = polygon.Flatten().Single().Points.Span.ToArray(); - - using TessellatedMultipolygon multipolygon = TessellatedMultipolygon.Create(polygon, MemoryAllocator); - VerifyRing(multipolygon[0], points, true, false); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/TopologyUtilitiesTests.cs b/tests/ImageSharp.Drawing.Tests/Shapes/Scan/TopologyUtilitiesTests.cs deleted file mode 100644 index 729274f58..000000000 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Scan/TopologyUtilitiesTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Shapes.Helpers; - -namespace SixLabors.ImageSharp.Drawing.Tests.Shapes.Scan; - -public class TopologyUtilitiesTests -{ - private static PointF[] CreateTestPoints() - => PolygonFactory.CreatePointArray( - (10, 0), - (20, 0), - (20, 30), - (10, 30), - (10, 20), - (0, 20), - (0, 10), - (10, 10), - (10, 0)); - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void EnsureOrientation_Positive(bool isPositive) - { - PointF[] expected = CreateTestPoints(); - PointF[] polygon = expected.CloneArray(); - - if (!isPositive) - { - polygon.AsSpan().Reverse(); - } - - TopologyUtilities.EnsureOrientation(polygon, 1); - - Assert.Equal(expected, polygon); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void EnsureOrientation_Negative(bool isNegative) - { - PointF[] expected = CreateTestPoints(); - expected.AsSpan().Reverse(); - - PointF[] polygon = expected.CloneArray(); - - if (!isNegative) - { - polygon.AsSpan().Reverse(); - } - - TopologyUtilities.EnsureOrientation(polygon, -1); - - Assert.Equal(expected, polygon); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/SvgPath.cs b/tests/ImageSharp.Drawing.Tests/Shapes/SvgPath.cs deleted file mode 100644 index 3b8ed47a5..000000000 --- a/tests/ImageSharp.Drawing.Tests/Shapes/SvgPath.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Processing; -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.Shapes; - -public class SvgPath -{ - [Theory] - [WithBlankImage(110, 70, PixelTypes.Rgba32, "M20,30 L40,5 L60,30 L80, 55 L100, 30", "zag")] - [WithBlankImage(110, 50, PixelTypes.Rgba32, "M20,30 Q40,5 60,30 T100,30", "wave")] - [WithBlankImage(500, 400, PixelTypes.Rgba32, "M10,350 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25", "bumpy")] - [WithBlankImage(500, 400, PixelTypes.Rgba32, "M300,200 h-150 a150,150 0 1,0 150,-150 z", "pie_small")] - [WithBlankImage(500, 400, PixelTypes.Rgba32, "M275,175 v-150 a150,150 0 0,0 -150,150 z", "pie_big")] - [WithBlankImage(100, 100, PixelTypes.Rgba32, "M50,50 L50,20 L80,50 z M40,60 L40,90 L10,60 z", "arrows")] - [WithBlankImage(500, 400, PixelTypes.Rgba32, "M 10 315 L 110 215 A 30 50 0 0 1 162.55 162.45 L 172.55 152.45 A 30 50 -45 0 1 215.1 109.9 L 315 10", "chopped_oval")] - public void RenderSvgPath(TestImageProvider provider, string svgPath, string exampleImageKey) - where TPixel : unmanaged, IPixel - { - bool parsed = Path.TryParseSvgPath(svgPath, out IPath path); - Assert.True(parsed); - - provider.RunValidatingProcessorTest( - c => c.Fill(Color.White).Draw(Color.Red, 5, path), - new { type = exampleImageKey }, - comparer: ImageComparer.TolerantPercentage(0.0035F)); // NET 472 x86 requires higher percentage - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/TestShapes.cs b/tests/ImageSharp.Drawing.Tests/Shapes/TestShapes.cs index a9f82b028..7eb1cfb80 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/TestShapes.cs +++ b/tests/ImageSharp.Drawing.Tests/Shapes/TestShapes.cs @@ -20,7 +20,7 @@ public static IPath IrisSegment(int rotationPos) new Vector2(78.26f, 97.0461f))).Translate(center - segmentRotationCenter); float angle = rotationPos * ((float)Math.PI / 3); - return segment.Transform(Matrix3x2.CreateRotation(angle, center)); + return segment.Transform(new Matrix4x4(Matrix3x2.CreateRotation(angle, center))); } public static IPath IrisSegment(float size, int rotationPos) @@ -39,9 +39,9 @@ public static IPath IrisSegment(float size, int rotationPos) float angle = rotationPos * ((float)Math.PI / 3); - IPath rotated = segment.Transform(Matrix3x2.CreateRotation(angle, center)); + IPath rotated = segment.Transform(new Matrix4x4(Matrix3x2.CreateRotation(angle, center))); - Matrix3x2 scaler = Matrix3x2.CreateScale(scalingFactor, Vector2.Zero); + Matrix4x4 scaler = Matrix4x4.CreateScale(scalingFactor); IPath scaled = rotated.Transform(scaler); return scaled; diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/Attributes/WebGPUFactAttribute.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/Attributes/WebGPUFactAttribute.cs new file mode 100644 index 000000000..18eac23f1 --- /dev/null +++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/Attributes/WebGPUFactAttribute.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Drawing.Processing.Backends; + +namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities.Attributes; + +/// +/// A that skips when WebGPU compute is not available on the current system. +/// +public class WebGPUFactAttribute : FactAttribute +{ + public WebGPUFactAttribute() + { + if (!WebGPUProbe.IsComputeSupported) + { + this.Skip = "WebGPU compute is not available on this system."; + } + } +} + +/// +/// A that skips when WebGPU compute is not available on the current system. +/// +public class WebGPUTheoryAttribute : TheoryAttribute +{ + public WebGPUTheoryAttribute() + { + if (!WebGPUProbe.IsComputeSupported) + { + this.Skip = "WebGPU compute is not available on this system."; + } + } +} + +/// +/// Caches the result of the WebGPU compute pipeline probe. +/// The backend's already performs +/// a full out-of-process probe via the internal RemoteExecutor, so we simply +/// instantiate the backend and check its result. +/// +internal static class WebGPUProbe +{ + private static bool? computeSupported; + + internal static bool IsComputeSupported => computeSupported ??= Probe(); + + private static bool Probe() + { + try + { + using WebGPUDrawingBackend backend = new(); + return backend.IsSupported; + } + catch + { + return false; + } + } +} diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/DebugDraw.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/DebugDraw.cs index 53c15901b..2e0de6699 100644 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/DebugDraw.cs +++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/DebugDraw.cs @@ -27,12 +27,16 @@ public void Polygon(IPath path, float gridSize = 10f, float scale = 10f, [Caller return; } - path = path.Transform(Matrix3x2.CreateScale(scale) * Matrix3x2.CreateTranslation(gridSize, gridSize)); + path = path.Transform(Matrix4x4.CreateScale(scale) * Matrix4x4.CreateTranslation(gridSize, gridSize, 0)); RectangleF bounds = path.Bounds; gridSize *= scale; using Image img = new Image((int)(bounds.Right + (2 * gridSize)), (int)(bounds.Bottom + (2 * gridSize))); - img.Mutate(ctx => DrawGrid(ctx.Fill(TestBrush, path), bounds, gridSize)); + img.Mutate(ctx => ctx.ProcessWithCanvas(canvas => + { + canvas.Fill(TestBrush, path); + DrawGrid(canvas, bounds, gridSize); + })); string outDir = TestEnvironment.CreateOutputDirectory(this.outputDir); string outFile = System.IO.Path.Combine(outDir, testMethod + ".png"); @@ -41,18 +45,18 @@ public void Polygon(IPath path, float gridSize = 10f, float scale = 10f, [Caller private static PointF P(float x, float y) => new(x, y); - private static void DrawGrid(IImageProcessingContext ctx, RectangleF rect, float gridSize) + private static void DrawGrid(IDrawingCanvas canvas, RectangleF rect, float gridSize) { for (float x = rect.Left; x <= rect.Right; x += gridSize) { PointF[] line = [P(x, rect.Top), P(x, rect.Bottom)]; - ctx.DrawLine(GridPen, line); + canvas.DrawLine(GridPen, line); } for (float y = rect.Top; y <= rect.Bottom; y += gridSize) { PointF[] line = [P(rect.Left, y), P(rect.Right, y)]; - ctx.DrawLine(GridPen, line); + canvas.DrawLine(GridPen, line); } } } diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/GraphicsOptionsComparer.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/GraphicsOptionsComparer.cs index fcdb3f9c2..c58fafc40 100644 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/GraphicsOptionsComparer.cs +++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/GraphicsOptionsComparer.cs @@ -16,6 +16,7 @@ public bool Equals(GraphicsOptions x, GraphicsOptions y) return x.AlphaCompositionMode == y.AlphaCompositionMode && x.Antialias == y.Antialias + && x.AntialiasThreshold == y.AntialiasThreshold && x.BlendPercentage == y.BlendPercentage && x.ColorBlendingMode == y.ColorBlendingMode; } diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/PolygonFactory.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/PolygonFactory.cs index e0bdfa02f..5d4920e61 100644 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/PolygonFactory.cs +++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/PolygonFactory.cs @@ -19,7 +19,7 @@ internal static class PolygonFactory // based on: // https://github.com/SixLabors/ImageSharp.Drawing/issues/15#issuecomment-521061283 - public static IReadOnlyList GetGeoJsonPoints(Feature geometryOwner, Matrix3x2 transform) + public static IReadOnlyList GetGeoJsonPoints(Feature geometryOwner, Matrix4x4 transform) { List result = []; IGeometryObject geometry = geometryOwner.Geometry; @@ -64,14 +64,14 @@ PointF PositionToPointF(IPosition pos) } } - public static PointF[][] GetGeoJsonPoints(string geoJsonContent, Matrix3x2 transform) + public static PointF[][] GetGeoJsonPoints(string geoJsonContent, Matrix4x4 transform) { FeatureCollection features = JsonConvert.DeserializeObject(geoJsonContent); return features.Features.SelectMany(f => GetGeoJsonPoints(f, transform)).ToArray(); } public static PointF[][] GetGeoJsonPoints(string geoJsonContent) => - GetGeoJsonPoints(geoJsonContent, Matrix3x2.Identity); + GetGeoJsonPoints(geoJsonContent, Matrix4x4.Identity); public static Polygon CreatePolygon(params (float X, float Y)[] coords) => new(new LinearLineSegment(CreatePointArray(coords))) diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs deleted file mode 100644 index f1e9a7cbc..000000000 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.InteropServices; -using ImageMagick; -using ImageMagick.Formats; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ReferenceCodecs; - -public class MagickReferenceDecoder : ImageDecoder -{ - private readonly IImageFormat imageFormat; - private readonly bool validate; - - public MagickReferenceDecoder(IImageFormat imageFormat) - : this(imageFormat, true) - { - } - - public MagickReferenceDecoder(IImageFormat imageFormat, bool validate) - { - this.imageFormat = imageFormat; - this.validate = validate; - } - - public static MagickReferenceDecoder Png { get; } = new(PngFormat.Instance); - - public static MagickReferenceDecoder Bmp { get; } = new(BmpFormat.Instance); - - public static MagickReferenceDecoder Jpeg { get; } = new(JpegFormat.Instance); - - public static MagickReferenceDecoder Tiff { get; } = new(TiffFormat.Instance); - - public static MagickReferenceDecoder WebP { get; } = new(WebpFormat.Instance); - - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - ImageMetadata metadata = new(); - - Configuration configuration = options.Configuration; - BmpReadDefines bmpReadDefines = new() - { - IgnoreFileSize = !this.validate - }; - PngReadDefines pngReadDefines = new() - { - IgnoreCrc = !this.validate - }; - - MagickReadSettings settings = new() - { - FrameCount = (int)options.MaxFrames - }; - settings.SetDefines(bmpReadDefines); - settings.SetDefines(pngReadDefines); - - using MagickImageCollection magickImageCollection = new(stream, settings); - List> framesList = []; - foreach (IMagickImage magicFrame in magickImageCollection) - { - ImageFrame frame = new(configuration, (int)magicFrame.Width, (int)magicFrame.Height); - framesList.Add(frame); - - MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; - - using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); - if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - FromRgba32Bytes(configuration, data, framePixels); - } - else if (magicFrame.Depth is 16 or 14) - { - if (this.imageFormat is PngFormat png) - { - metadata.GetPngMetadata().BitDepth = PngBitDepth.Bit16; - } - - ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); - Span bytes = MemoryMarshal.Cast(data.AsSpan()); - FromRgba64Bytes(configuration, bytes, framePixels); - } - else - { - throw new InvalidOperationException(); - } - } - - return ReferenceCodecUtilities.EnsureDecodedMetadata(new Image(configuration, metadata, framesList), this.imageFormat); - } - - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); - - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - using Image image = this.Decode(options, stream, cancellationToken); - ImageMetadata metadata = image.Metadata; - return new ImageInfo(image.Size, metadata, new List(image.Frames.Select(x => x.Metadata))) - { - PixelType = metadata.GetDecodedPixelTypeInfo() - }; - } - - private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - Span sourcePixels = MemoryMarshal.Cast(rgbaBytes); - foreach (Memory m in destinationGroup) - { - Span destBuffer = m.Span; - PixelOperations.Instance.FromRgba32( - configuration, - sourcePixels[..destBuffer.Length], - destBuffer); - sourcePixels = sourcePixels[destBuffer.Length..]; - } - } - - private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - foreach (Memory m in destinationGroup) - { - Span destBuffer = m.Span; - PixelOperations.Instance.FromRgba64Bytes( - configuration, - rgbaBytes, - destBuffer, - destBuffer.Length); - rgbaBytes = rgbaBytes[(destBuffer.Length * 8)..]; - } - } -} diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs deleted file mode 100644 index a4df368b9..000000000 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/ReferenceCodecUtilities.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Pbm; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Qoi; -using SixLabors.ImageSharp.Formats.Tga; -using SixLabors.ImageSharp.Formats.Tiff; -using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ReferenceCodecs; - -internal static class ReferenceCodecUtilities -{ - /// - /// Ensures that the metadata is properly initialized for reference and test encoders which cannot initialize - /// metadata in the same manner as our built in decoders. - /// - /// The type of pixel format. - /// The decoded image. - /// The image format - /// The format is unknown. - public static Image EnsureDecodedMetadata(Image image, IImageFormat format) - where TPixel : unmanaged, IPixel - { - if (image.Metadata.DecodedImageFormat is null) - { - image.Metadata.DecodedImageFormat = format; - } - - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.DecodedImageFormat = format; - } - - switch (format) - { - case BmpFormat: - image.Metadata.GetBmpMetadata(); - break; - case GifFormat: - image.Metadata.GetGifMetadata(); - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.GetGifMetadata(); - } - - break; - case JpegFormat: - image.Metadata.GetJpegMetadata(); - break; - case PbmFormat: - image.Metadata.GetPbmMetadata(); - break; - case PngFormat: - image.Metadata.GetPngMetadata(); - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.GetPngMetadata(); - } - - break; - case QoiFormat: - image.Metadata.GetQoiMetadata(); - break; - case TgaFormat: - image.Metadata.GetTgaMetadata(); - break; - case TiffFormat: - image.Metadata.GetTiffMetadata(); - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.GetTiffMetadata(); - } - - break; - case WebpFormat: - image.Metadata.GetWebpMetadata(); - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.GetWebpMetadata(); - } - - break; - } - - return image; - } -} diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs deleted file mode 100644 index ab9b698f7..000000000 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers; -using System.Drawing; -using System.Drawing.Imaging; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ReferenceCodecs; - -/// -/// Provides methods to convert to/from System.Drawing bitmaps. -/// -public static class SystemDrawingBridge -{ - /// - /// Returns an image from the given System.Drawing bitmap. - /// - /// The pixel format. - /// The input bitmap. - /// Thrown if the image pixel format is not of type - internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : unmanaged, IPixel - { - int w = bmp.Width; - int h = bmp.Height; - - System.Drawing.Rectangle fullRect = new(0, 0, w, h); - - if (bmp.PixelFormat != PixelFormat.Format32bppArgb) - { - throw new ArgumentException( - $"{nameof(From32bppArgbSystemDrawingBitmap)} : pixel format should be {PixelFormat.Format32bppArgb}!", - nameof(bmp)); - } - - BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); - Image image = new(w, h); - try - { - byte* sourcePtrBase = (byte*)data.Scan0; - - long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Bgra32); - - Configuration configuration = image.Configuration; - image.ProcessPixelRows(accessor => - { - using IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w); - fixed (Bgra32* destPtr = &workBuffer.GetReference()) - { - for (int y = 0; y < h; y++) - { - Span row = accessor.GetRowSpan(y); - - byte* sourcePtr = sourcePtrBase + (data.Stride * y); - - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - PixelOperations.Instance.FromBgra32( - configuration, - workBuffer.GetSpan().Slice(0, w), - row); - } - } - }); - } - finally - { - bmp.UnlockBits(data); - } - - return image; - } - - /// - /// Returns an image from the given System.Drawing bitmap. - /// - /// The pixel format. - /// The input bitmap. - /// Thrown if the image pixel format is not of type - internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : unmanaged, IPixel - { - int w = bmp.Width; - int h = bmp.Height; - - System.Drawing.Rectangle fullRect = new(0, 0, w, h); - - if (bmp.PixelFormat != PixelFormat.Format24bppRgb) - { - throw new ArgumentException( - $"{nameof(From24bppRgbSystemDrawingBitmap)}: pixel format should be {PixelFormat.Format24bppRgb}!", - nameof(bmp)); - } - - BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); - Image image = new(w, h); - try - { - byte* sourcePtrBase = (byte*)data.Scan0; - - long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Bgr24); - - Configuration configuration = image.Configuration; - Buffer2D imageBuffer = image.GetRootFramePixelBuffer(); - - using IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w); - fixed (Bgr24* destPtr = &workBuffer.GetReference()) - { - for (int y = 0; y < h; y++) - { - Span row = imageBuffer.DangerousGetRowSpan(y); - - byte* sourcePtr = sourcePtrBase + (data.Stride * y); - - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - PixelOperations.Instance.FromBgr24(configuration, workBuffer.GetSpan().Slice(0, w), row); - } - } - } - finally - { - bmp.UnlockBits(data); - } - - return image; - } - - internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) - where TPixel : unmanaged, IPixel - { - Configuration configuration = image.Configuration; - int w = image.Width; - int h = image.Height; - - Bitmap resultBitmap = new(w, h, PixelFormat.Format32bppArgb); - System.Drawing.Rectangle fullRect = new(0, 0, w, h); - BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); - try - { - byte* destPtrBase = (byte*)data.Scan0; - - long destRowByteCount = data.Stride; - long sourceRowByteCount = w * sizeof(Bgra32); - image.ProcessPixelRows(accessor => - { - using IMemoryOwner workBuffer = image.Configuration.MemoryAllocator.Allocate(w); - fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) - { - for (int y = 0; y < h; y++) - { - Span row = accessor.GetRowSpan(y); - PixelOperations.Instance.ToBgra32(configuration, row, workBuffer.GetSpan()); - byte* destPtr = destPtrBase + (data.Stride * y); - - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - } - } - }); - } - finally - { - resultBitmap.UnlockBits(data); - } - - return resultBitmap; - } -} diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs deleted file mode 100644 index afb0d38f2..000000000 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#pragma warning disable CA1416 // Validate platform compatibility -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.PixelFormats; -using SDBitmap = System.Drawing.Bitmap; - -namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ReferenceCodecs; - -public class SystemDrawingReferenceDecoder : ImageDecoder -{ - private readonly IImageFormat imageFormat; - - public SystemDrawingReferenceDecoder(IImageFormat imageFormat) - => this.imageFormat = imageFormat; - - public static SystemDrawingReferenceDecoder Png { get; } = new(PngFormat.Instance); - - public static SystemDrawingReferenceDecoder Bmp { get; } = new(BmpFormat.Instance); - - protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - using Image image = this.Decode(options, stream, cancellationToken); - ImageMetadata metadata = image.Metadata; - return new ImageInfo(image.Size, metadata, new List(image.Frames.Select(x => x.Metadata))) - { - PixelType = metadata.GetDecodedPixelTypeInfo() - }; - } - - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - using SDBitmap sourceBitmap = new(stream); - if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) - { - return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sourceBitmap); - } - - using SDBitmap convertedBitmap = new( - sourceBitmap.Width, - sourceBitmap.Height, - System.Drawing.Imaging.PixelFormat.Format32bppArgb); - using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(convertedBitmap)) - { - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - - g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height); - } - - return ReferenceCodecUtilities.EnsureDecodedMetadata( - SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap), - this.imageFormat); - } - - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); -} diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs deleted file mode 100644 index b3cf88c96..000000000 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Drawing; -using System.Drawing.Imaging; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ReferenceCodecs; - -public class SystemDrawingReferenceEncoder : IImageEncoder -{ - private readonly ImageFormat imageFormat; - - public SystemDrawingReferenceEncoder(ImageFormat imageFormat) - => this.imageFormat = imageFormat; - - public static SystemDrawingReferenceEncoder Png { get; } = new(ImageFormat.Png); - - public static SystemDrawingReferenceEncoder Bmp { get; } = new(ImageFormat.Bmp); - - public bool SkipMetadata { get; init; } - - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - using Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image); - sdBitmap.Save(stream, this.imageFormat); - } - - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using (Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) - { - sdBitmap.Save(stream, this.imageFormat); - } - - return Task.CompletedTask; - } -} diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/TestEnvironment.Formats.cs index 01598903b..515732a68 100644 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -1,22 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ReferenceCodecs; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Formats.Tga; using IOPath = System.IO.Path; namespace SixLabors.ImageSharp.Drawing.Tests; public static partial class TestEnvironment { - private static readonly Lazy ConfigurationLazy = new(CreateDefaultConfiguration); - - internal static Configuration Configuration => ConfigurationLazy.Value; + internal static Configuration Configuration => Configuration.Default; internal static IImageDecoder GetReferenceDecoder(string filePath) { @@ -38,42 +30,4 @@ internal static IImageFormat GetImageFormat(string filePath) return format; } - - private static void ConfigureCodecs( - this Configuration cfg, - IImageFormat imageFormat, - IImageDecoder decoder, - IImageEncoder encoder, - IImageFormatDetector detector) - { - cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder); - cfg.ImageFormatsManager.SetEncoder(imageFormat, encoder); - cfg.ImageFormatsManager.AddImageFormatDetector(detector); - } - - private static Configuration CreateDefaultConfiguration() - { - Configuration cfg = new( - new JpegConfigurationModule(), - new GifConfigurationModule(), - new TgaConfigurationModule()); - - // Magick codecs should work on all platforms - IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new PngEncoder(); - IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); - - cfg.ConfigureCodecs( - PngFormat.Instance, - MagickReferenceDecoder.Png, - pngEncoder, - new PngImageFormatDetector()); - - cfg.ConfigureCodecs( - BmpFormat.Instance, - IsWindows ? SystemDrawingReferenceDecoder.Bmp : MagickReferenceDecoder.Bmp, - bmpEncoder, - new BmpImageFormatDetector()); - - return cfg; - } } diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Structs/TestPoint.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/TestPoint.cs similarity index 94% rename from tests/ImageSharp.Drawing.Tests/Shapes/Structs/TestPoint.cs rename to tests/ImageSharp.Drawing.Tests/TestUtilities/TestPoint.cs index 3606891ae..74710b3aa 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Structs/TestPoint.cs +++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/TestPoint.cs @@ -4,7 +4,7 @@ using System.Numerics; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Drawing.Tests; +namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities; [Serializable] public class TestPoint : IXunitSerializable diff --git a/tests/ImageSharp.Drawing.Tests/Shapes/Structs/TestSize.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/TestSize.cs similarity index 94% rename from tests/ImageSharp.Drawing.Tests/Shapes/Structs/TestSize.cs rename to tests/ImageSharp.Drawing.Tests/TestUtilities/TestSize.cs index a5dcb4880..3a2882ecd 100644 --- a/tests/ImageSharp.Drawing.Tests/Shapes/Structs/TestSize.cs +++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/TestSize.cs @@ -3,7 +3,7 @@ using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Drawing.Tests; +namespace SixLabors.ImageSharp.Drawing.Tests.TestUtilities; [Serializable] public class TestSize : IXunitSerializable diff --git a/tests/ImageSharp.Drawing.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Drawing.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 8aa1f6a8a..e1c7cf166 100644 --- a/tests/ImageSharp.Drawing.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Drawing.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Drawing.Tests.TestUtilities.ReferenceCodecs; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -48,66 +47,24 @@ public void GetReferenceOutputFileName() Assert.Contains(TestEnvironment.ReferenceOutputDirectoryFullPath, expected); } - [Theory] - [InlineData("lol/foo.png", typeof(SystemDrawingReferenceEncoder))] - [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceEncoder))] - [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] - [InlineData("lol/Baz.gif", typeof(GifEncoder))] - public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) - { - if (!TestEnvironment.IsWindows) - { - return; - } - - IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); - Assert.IsType(expectedEncoderType, encoder); - } - - [Theory] - [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] - [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))] - [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] - [InlineData("lol/Baz.gif", typeof(GifDecoder))] - public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) - { - if (!TestEnvironment.IsWindows) - { - return; - } - - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); - Assert.IsType(expectedDecoderType, decoder); - } - [Theory] [InlineData("lol/foo.png", typeof(PngEncoder))] [InlineData("lol/Rofl.bmp", typeof(BmpEncoder))] [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] [InlineData("lol/Baz.gif", typeof(GifEncoder))] - public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) + public void GetReferenceEncoder_ReturnsCorrectEncoders(string fileName, Type expectedEncoderType) { - if (!TestEnvironment.IsLinux) - { - return; - } - IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); Assert.IsType(expectedEncoderType, encoder); } [Theory] - [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] - [InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))] + [InlineData("lol/foo.png", typeof(PngDecoder))] + [InlineData("lol/Rofl.bmp", typeof(BmpDecoder))] [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] [InlineData("lol/Baz.gif", typeof(GifDecoder))] - public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) + public void GetReferenceDecoder_ReturnsCorrectDecoders(string fileName, Type expectedDecoderType) { - if (!TestEnvironment.IsLinux) - { - return; - } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); Assert.IsType(expectedDecoderType, decoder); } diff --git a/tests/ImageSharp.Drawing.Tests/Utilities/IntersectTests.cs b/tests/ImageSharp.Drawing.Tests/Utilities/IntersectTests.cs deleted file mode 100644 index 485413515..000000000 --- a/tests/ImageSharp.Drawing.Tests/Utilities/IntersectTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using SixLabors.ImageSharp.Drawing.Utilities; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Utils; - -public class IntersectTests -{ - public static TheoryData<(float X, float Y), (float X, float Y), (float X, float Y), (float X, float Y), (float X, float Y)?> LineSegmentToLineSegment_Data = - new() - { - { (0, 0), (2, 3), (1, 3), (1, 0), (1, 1.5f) }, - { (3, 1), (3, 3), (3, 2), (4, 2), (3, 2) }, - { (1, -3), (3, -1), (3, -4), (2, -2), (2, -2) }, - { (0, 0), (2, 1), (2, 1.0001f), (5, 2), (2, 1) }, // Robust to inaccuracies - { (0, 0), (2, 3), (1, 3), (1, 2), null }, - { (-3, 3), (-1, 3), (-3, 2), (-1, 2), null }, - { (-4, 3), (-4, 1), (-5, 3), (-5, 1), null }, - { (0, 0), (4, 1), (4, 1), (8, 2), null }, // Collinear intersections are ignored - { (0, 0), (4, 1), (4, 1.0001f), (8, 2), null }, // Collinear intersections are ignored - }; - - [Theory] - [MemberData(nameof(LineSegmentToLineSegment_Data))] - public void LineSegmentToLineSegmentNoCollinear( - (float X, float Y) a0, - (float X, float Y) a1, - (float X, float Y) b0, - (float X, float Y) b1, - (float X, float Y)? expected) - { - Vector2 ip = default; - - bool result = Intersect.LineSegmentToLineSegmentIgnoreCollinear(P(a0), P(a1), P(b0), P(b1), ref ip); - Assert.Equal(result, expected.HasValue); - if (expected.HasValue) - { - Assert.Equal(P(expected.Value), ip, new ApproximateFloatComparer(1e-3f)); - } - - static Vector2 P((float X, float Y) p) => new(p.X, p.Y); - } -} diff --git a/tests/ImageSharp.Drawing.Tests/Utilities/NumericUtilitiesTests.cs b/tests/ImageSharp.Drawing.Tests/Utilities/NumericUtilitiesTests.cs deleted file mode 100644 index 206985699..000000000 --- a/tests/ImageSharp.Drawing.Tests/Utilities/NumericUtilitiesTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Drawing.Utilities; - -namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Utils; - -public class NumericUtilitiesTests -{ - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(3)] - [InlineData(7)] - [InlineData(8)] - [InlineData(13)] - [InlineData(130)] - public void AddToAllElements(int length) - { - float[] values = Enumerable.Range(0, length).Select(v => (float)v).ToArray(); - - const float val = 13.4321f; - float[] expected = values.Select(x => x + val).ToArray(); - values.AsSpan().AddToAllElements(val); - - Assert.Equal(expected, values); - } -} diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/ClearAlwaysOverridesPreviousColor_Blue.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/ClearAlwaysOverridesPreviousColor_Blue.png deleted file mode 100644 index dad8ece49..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/ClearAlwaysOverridesPreviousColor_Blue.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/ClearAlwaysOverridesPreviousColor_Khaki.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/ClearAlwaysOverridesPreviousColor_Khaki.png deleted file mode 100644 index 3fc305e9f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/ClearAlwaysOverridesPreviousColor_Khaki.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_Argb32.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_Argb32.png deleted file mode 100644 index 1410827bc..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_Argb32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png deleted file mode 100644 index 1410827bc..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_RgbaVector.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_RgbaVector.png deleted file mode 100644 index 1410827bc..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSinglePixelType_RgbaVector.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank16x7.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank16x7.png deleted file mode 100644 index e20907b99..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank16x7.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank1x1.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank1x1.png deleted file mode 100644 index 4e4ee1ee1..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank1x1.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank33x32.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank33x32.png deleted file mode 100644 index 31965cc3a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank33x32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank400x500.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank400x500.png deleted file mode 100644 index f3c6b080b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank400x500.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank7x4.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank7x4.png deleted file mode 100644 index 8914b9c49..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/DoesNotDependOnSize_Blank7x4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png deleted file mode 100644 index 4fdb95635..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png deleted file mode 100644 index b56cd2f34..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png deleted file mode 100644 index 4fdb95635..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png deleted file mode 100644 index b56cd2f34..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Blue.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Blue.png deleted file mode 100644 index c5ad73f42..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Blue.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Khaki.png b/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Khaki.png deleted file mode 100644 index 4860936c1..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/ClearSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Khaki.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_ConstrainsOperationToClipBounds.png b/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_ConstrainsOperationToClipBounds.png deleted file mode 100644 index 969d80f9b..000000000 --- a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_ConstrainsOperationToClipBounds.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:790a9e156bee55ddb3d40dd743eafa2a4b0129c43618fea3e99ffd875bd1d551 -size 39092 diff --git a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x-20_y-100.png b/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x-20_y-100.png deleted file mode 100644 index 9a791fde1..000000000 --- a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x-20_y-100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e44f9598e2f6c9a5f3aac6dcd73edb1a818d1e864fd154371b0d54ca075aa05e -size 3694 diff --git a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x-20_y-20.png b/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x-20_y-20.png deleted file mode 100644 index d60adda71..000000000 --- a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x-20_y-20.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ecf41b05a42a6f275524131bcaf89298a059e2a0aabbaf2348ce2ad036197ede -size 5013 diff --git a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x0_y0.png b/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x0_y0.png deleted file mode 100644 index ee0f3d4fd..000000000 --- a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x0_y0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15622bb81ee71518a2fa56e758f2df5fddee69e0a01f2617e0f67201e930553f -size 5356 diff --git a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x20_y20.png b/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x20_y20.png deleted file mode 100644 index 55715e2aa..000000000 --- a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x20_y20.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3739ab0effb4caf5e84add7c0c1d1cc3bbec0c1fb7e7d7826a818bf0976fbe4f -size 5446 diff --git a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x40_y60.png b/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x40_y60.png deleted file mode 100644 index 3d61682c4..000000000 --- a/tests/Images/ReferenceOutput/Drawing/ClipTests/Clip_offset_x40_y60.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bd217c38b95baedd42064b696d975805120d91561c8d77248b749d35c1fbcf75 -size 2315 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_HotPink_A150_T5.png b/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_HotPink_A150_T5.png deleted file mode 100644 index 13f33766a..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_HotPink_A150_T5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:174c98c137feb54c05fa59823af2a09fdade5d2ceb59e70e37c507dafcf6118f -size 4334 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_HotPink_A255_T5.png b/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_HotPink_A255_T5.png deleted file mode 100644 index 84a84ba79..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_HotPink_A255_T5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e531b54fbfcbcba2df2a3373734314a1644541a2faf8c15420c53a959bb57a7 -size 4613 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_Red_A255_T3.png b/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_Red_A255_T3.png deleted file mode 100644 index 6eaaee087..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_Red_A255_T3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1839a05c5eeb8c7b90758a7b9c3d2919a726a78eefd9de2728f5edf37a2018a -size 4613 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_White_A255_T1.5.png b/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_White_A255_T1.5.png deleted file mode 100644 index 8c376a110..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_White_A255_T1.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1057cf06d0acc8dd05883c14475210953827b0cf8cde751c8dc2bc8eedc6554d -size 4613 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_White_A255_T15.png b/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_White_A255_T15.png deleted file mode 100644 index 8c376a110..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawBezierTests/DrawBeziers_White_A255_T15.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1057cf06d0acc8dd05883c14475210953827b0cf8cde751c8dc2bc8eedc6554d -size 4613 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon.png b/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon.png deleted file mode 100644 index 0e1070e7b..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb552c825e6395c17eebfcc42be5b34cb0912bbbb0ada7689fdc80d1f5a22c9a -size 4466 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Dashed.png b/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Dashed.png deleted file mode 100644 index 0b71e15ae..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Dashed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1efe570c8fa8654a615adb12fe993316233dc7af7d317a4cc334ac86aa3f5a44 -size 8166 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Overlap.png b/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Overlap.png deleted file mode 100644 index 0e639df54..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Overlap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e75557d8e59a3aae917228cfd9b6a1251c5c4f771d08d8e16e2de99cb16d53d4 -size 6169 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Transparent.png b/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Transparent.png deleted file mode 100644 index 9a7f7901f..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawComplexPolygonTests/DrawComplexPolygon__Transparent.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57bd54dc3d42753e9d866785d8efa8ec0a79398de26325913973b005d40cd387 -size 4139 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(1).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(1).png deleted file mode 100644 index b3859ce0b..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(1).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:459dcb4b0e81dc7e850babc169510ac2298636c7a65c06802f104dca3ce87a45 -size 165 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(1)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(1)_NoAntialias.png deleted file mode 100644 index a10f7de61..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(1)_NoAntialias.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d2ab1c8fd901a5bede28a3036a4df9cb551e6a13c154a988da1700c89fb67b4 -size 161 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(5).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(5).png deleted file mode 100644 index f4d76fb04..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(5).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:635053b4ce23dafc080d4d9de9e808819d264461ee5e0543acfefb8c9a04d00e -size 187 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(5)_NoAntialias.png deleted file mode 100644 index ce158e063..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLinesInvalidPoints_Rgba32_T(5)_NoAntialias.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5482537fd9d8f4ad4c6e03ae1ae0d7f8035ecb29f541778009653ab7796510dd -size 173 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_DashDotDot_Rgba32_Black_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_DashDotDot_Rgba32_Black_A(1)_T(5)_NoAntialias.png deleted file mode 100644 index 8c8f5d483..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_DashDotDot_Rgba32_Black_A(1)_T(5)_NoAntialias.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4571c05512d80efe9a04a9ef75ee14511f5cd0182ae1630347fd6d53cc4e30f5 -size 996 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_DashDot_Rgba32_Yellow_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_DashDot_Rgba32_Yellow_A(1)_T(5)_NoAntialias.png deleted file mode 100644 index cb1e62505..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_DashDot_Rgba32_Yellow_A(1)_T(5)_NoAntialias.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d6be109fd0778212b7c92ea4b83af2c9799d383e7cd674514251a7d1e20f45a -size 1011 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Dash_Rgba32_White_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Dash_Rgba32_White_A(1)_T(5)_NoAntialias.png deleted file mode 100644 index 15adb7c31..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Dash_Rgba32_White_A(1)_T(5)_NoAntialias.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7b54785fecd7ddc017876f0bdc0190e6a43987c2d69c5150a29b640172ae0d65 -size 1072 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Dot_Rgba32_LightGreen_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Dot_Rgba32_LightGreen_A(1)_T(5)_NoAntialias.png deleted file mode 100644 index 863cf7c7e..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Dot_Rgba32_LightGreen_A(1)_T(5)_NoAntialias.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff0ce825507b2e95b49c8eebfde000b10c890e090cd7bb533397fbb62767178c -size 903 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapButt_Rgba32_Yellow_A(1)_T(5).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapButt_Rgba32_Yellow_A(1)_T(5).png deleted file mode 100644 index a2902c5a1..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapButt_Rgba32_Yellow_A(1)_T(5).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed1478e8fc206b1beaa69a7df49cfbb26adcb395c21bbea85657b9e647f9ef14 -size 2874 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapRound_Rgba32_Yellow_A(1)_T(5).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapRound_Rgba32_Yellow_A(1)_T(5).png deleted file mode 100644 index 8ec380476..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapRound_Rgba32_Yellow_A(1)_T(5).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3d0acf65b85c8e58096e281b309596d51785502e17d8620ca71ee36b5a46943 -size 4215 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapSquare_Rgba32_Yellow_A(1)_T(5).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapSquare_Rgba32_Yellow_A(1)_T(5).png deleted file mode 100644 index 78098f2df..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_EndCapSquare_Rgba32_Yellow_A(1)_T(5).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c65de578e11a666d95ba9f3f01d19d34f2c376219babcf8a7d032e2c55a43558 -size 3106 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleMiter_Rgba32_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleMiter_Rgba32_Yellow_A(1)_T(10).png deleted file mode 100644 index f81d2f0a6..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleMiter_Rgba32_Yellow_A(1)_T(10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:246709450f82f00a2008cda56661d313783913a0db9a2f87741abc97dd662eb1 -size 2412 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleRound_Rgba32_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleRound_Rgba32_Yellow_A(1)_T(10).png deleted file mode 100644 index bba63ff53..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleRound_Rgba32_Yellow_A(1)_T(10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:549fec09d5fc231dfc9ac7c72f69cea66be07303216467e335434d412ceca67a -size 2511 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleSquare_Rgba32_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleSquare_Rgba32_Yellow_A(1)_T(10).png deleted file mode 100644 index a9e1d1018..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_JointStyleSquare_Rgba32_Yellow_A(1)_T(10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f2d08e712955e19d82bb5a38247f1a7cbda0bc79e9dfa76514e08e0e90a89ac9 -size 2521 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Bgr24_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Bgr24_Yellow_A(1)_T(10).png deleted file mode 100644 index 28e62bc17..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Bgr24_Yellow_A(1)_T(10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:95e68c3f3c108915ccc89ccbb13d4acc089aad3f7d8eff38a263c3016d233511 -size 2445 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Rgba32_White_A(0.6)_T(10).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Rgba32_White_A(0.6)_T(10).png deleted file mode 100644 index 6ae3222d4..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Rgba32_White_A(0.6)_T(10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:575650693f22358528fa2514ce474a1b50b228dff7ec00ed8c695981ade6f12e -size 2300 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Rgba32_White_A(1)_T(2.5).png b/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Rgba32_White_A(1)_T(2.5).png deleted file mode 100644 index 9d82ad2cc..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Rgba32_White_A(1)_T(2.5).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:281d3c8349ea7e15961d1e0be5c5a0c4aad743295381f89bd3f9f7f43a02ac24 -size 2363 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingAddArc_359.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingAddArc_359.png deleted file mode 100644 index c6439fdc5..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingAddArc_359.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4edce89e09ede18430cff57ff74c21bccbac076c5d015d0ca77d039fc586fc62 -size 1747 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingAddArc_360.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingAddArc_360.png deleted file mode 100644 index 96098fbf1..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingAddArc_360.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:03eb9645a7fb021bd30723dc4a4a8b1bc2900f604fef165a6ac472bd0a25c537 -size 1713 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingArcTo_False.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingArcTo_False.png deleted file mode 100644 index ceeae75f2..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingArcTo_False.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da8ba7c8929209a833524ff5cfb59ecded92d7a95b3022bbda80816aff313c31 -size 1559 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingArcTo_True.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingArcTo_True.png deleted file mode 100644 index ceeae75f2..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawCircleUsingArcTo_True.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da8ba7c8929209a833524ff5cfb59ecded92d7a95b3022bbda80816aff313c31 -size 1559 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPathClippedOnTop.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPathClippedOnTop.png deleted file mode 100644 index 3d94259f7..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPathClippedOnTop.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b74c1eecb18745be829c3effe3f65fd3a965dd624b0098400342360d7d39dfb7 -size 203 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_HotPink_A150_T5.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_HotPink_A150_T5.png deleted file mode 100644 index 2bd89ce82..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_HotPink_A150_T5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c79f14ec9d1e1042a9f0c0e09ed1f355889bdd74461050c3529e7c2ac677f26 -size 7725 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_HotPink_A255_T5.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_HotPink_A255_T5.png deleted file mode 100644 index c5206d91b..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_HotPink_A255_T5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffae375183e7df6a7730206ba27dbfe1d94460ee4af4e5774932c72ee88f0bb6 -size 14745 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_Red_A255_T3.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_Red_A255_T3.png deleted file mode 100644 index c667647ef..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_Red_A255_T3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:87a6e83e4da825413890e9510bf6a3b516f7ca769e9a245583288c76ef6e31a2 -size 14295 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_White_A255_T1.5.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_White_A255_T1.5.png deleted file mode 100644 index 130ae7039..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_White_A255_T1.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0b90ebe9051af282603dd10e07e7743c8ba1ee81c4f56e163c46075a58678bc -size 7159 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_White_A255_T15.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_White_A255_T15.png deleted file mode 100644 index ff7d8b685..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/DrawPath_White_A255_T15.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4e5d00ab59f163347567cdafc7b1c37c66475dcc4e84de5685214464097ee87a -size 7863 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/PathExtendingOffEdgeOfImageShouldNotBeCropped.png b/tests/Images/ReferenceOutput/Drawing/DrawPathTests/PathExtendingOffEdgeOfImageShouldNotBeCropped.png deleted file mode 100644 index 95b8be0e8..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPathTests/PathExtendingOffEdgeOfImageShouldNotBeCropped.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1fcfd5112a7e5c41d9c9ce257da4fdf5e60f76167f7a81cc6790c554b616e60 -size 5837 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Bgr24_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Bgr24_Yellow_A(1)_T(10).png deleted file mode 100644 index 141ca9492..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Bgr24_Yellow_A(1)_T(10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:acfc9b104be88bef18386bd3ef9faad56070f1f808aa4ec162dceec01a3c4352 -size 3841 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(0.6)_T(10).png b/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(0.6)_T(10).png deleted file mode 100644 index 2bbf451ed..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(0.6)_T(10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8be397a2c3ea3aeee259dc407633f0bf3f6146acda86a1d7bd8e75f4ffa42b7 -size 3492 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(1)_T(2.5).png b/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(1)_T(2.5).png deleted file mode 100644 index 609fc3579..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(1)_T(2.5).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:732040a526d7581c2d2842f0f3c35fed6f2266dd7793128d2bb023b8b986f937 -size 3902 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(1)_T(5)_NoAntialias.png deleted file mode 100644 index fb1965988..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Rgba32_White_A(1)_T(5)_NoAntialias.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ba9da410ee320f2de0f95a9b37abb1d9306a19e6e6e50ad8ada02766dbcc78bc -size 1264 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Transformed_Rgba32_BasicTestPattern250x350.png b/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Transformed_Rgba32_BasicTestPattern250x350.png deleted file mode 100644 index 87e3affc6..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawPolygon_Transformed_Rgba32_BasicTestPattern250x350.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89d4652a3e12deffc5eafb55d14111134ec8e3047ff43caf96d2ac6483cc0ca3 -size 8874 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawRectangularPolygon_Transformed_Rgba32_BasicTestPattern100x100.png b/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawRectangularPolygon_Transformed_Rgba32_BasicTestPattern100x100.png deleted file mode 100644 index 6f8346d15..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawPolygonTests/DrawRectangularPolygon_Transformed_Rgba32_BasicTestPattern100x100.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b39e13b16a16caf2bbb8a086fae6eecab8daf01f0b71cec7b6f6939393f554ac -size 601 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Clear_RegionAndPath_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Clear_RegionAndPath_MatchesReference_Rgba32.png new file mode 100644 index 000000000..a7d443758 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Clear_RegionAndPath_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39408da1bb6e4003ff0c2977b467fe169a07ff8797b5f99ec5d959cec298eff9 +size 3904 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Clear_WithClipPath_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Clear_WithClipPath_MatchesReference_Rgba32.png new file mode 100644 index 000000000..947c995b2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Clear_WithClipPath_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95492c39205f3e593703e7e70e80495c319e196e31c3d1749df5e6c644375a10 +size 11117 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/CreateRegion_LocalCoordinates_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/CreateRegion_LocalCoordinates_MatchesReference_Rgba32.png new file mode 100644 index 000000000..763a40e15 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/CreateRegion_LocalCoordinates_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33d2c4be22e1e7968f7c555dd3d39f927dbed3b3e960ca55fd197d5830720eb2 +size 2023 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/CreateRegion_NestedRegionsAndStateIsolation_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/CreateRegion_NestedRegionsAndStateIsolation_MatchesReference_Rgba32.png new file mode 100644 index 000000000..dcee461d4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/CreateRegion_NestedRegionsAndStateIsolation_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e00d17cb971258b9a281795775889b5b170e9a450e35f527acc0b2ac6e0c5d0 +size 12402 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawGlyphs_EmojiFont_MatchesReference_Rgba32_ColrV1-draw-glyphs.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawGlyphs_EmojiFont_MatchesReference_Rgba32_ColrV1-draw-glyphs.png new file mode 100644 index 000000000..617a48cc1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawGlyphs_EmojiFont_MatchesReference_Rgba32_ColrV1-draw-glyphs.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45bbd1bf6fc5881cedf0036bca2e122c4a9add1e3e6183c474d1f6889e2d791c +size 11014 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawGlyphs_EmojiFont_MatchesReference_Rgba32_Svg-draw-glyphs.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawGlyphs_EmojiFont_MatchesReference_Rgba32_Svg-draw-glyphs.png new file mode 100644 index 000000000..de894f52b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawGlyphs_EmojiFont_MatchesReference_Rgba32_Svg-draw-glyphs.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937de251b66cbb20c88d46b6e105c60725fb448d7fd9ad106c0e6f3439cb07ec +size 11014 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithClipPathAndTransform_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithClipPathAndTransform_MatchesReference_Rgba32.png new file mode 100644 index 000000000..ff66131a7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithClipPathAndTransform_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:266c91d9bdbb7714fbe84f372e1998220a1794f7b473cac4ce51e57b114a9052 +size 11333 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithRotationTransform_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithRotationTransform_MatchesReference_Rgba32.png new file mode 100644 index 000000000..41e7ad63f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithRotationTransform_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4a94515495d39d337393f563a87eefdf409ab13ac67fc217475b346bf80fb67 +size 4303 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithSourceClippingAndScaling_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithSourceClippingAndScaling_MatchesReference_Rgba32.png new file mode 100644 index 000000000..e836e72ea --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawImage_WithSourceClippingAndScaling_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:431a0e81f68c1052900a104702e139051df38cf2aace11df421e695dae7a1679 +size 627 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawPrimitiveHelpers_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawPrimitiveHelpers_MatchesReference_Rgba32.png new file mode 100644 index 000000000..5d9ac768f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawPrimitiveHelpers_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce7f2b5f7442f07d112cf5fd623a257b054953e5663d29263eb33e40bcb25ddf +size 9196 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_AlongPathWithOrigin_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_AlongPathWithOrigin_MatchesReference_Rgba32.png new file mode 100644 index 000000000..5915a5a10 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_AlongPathWithOrigin_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4907effeaccf759f2bdc47e589427a0b49a7264959934978b304058aaf79ed1c +size 11078 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_FillAndStroke_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_FillAndStroke_MatchesReference_Rgba32.png new file mode 100644 index 000000000..fc09d3204 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_FillAndStroke_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f4dfe9dcd6cd545d9f7fe407f37c8b473667021dec638447bc378d2ce2a7f1a +size 21028 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_Multiline_WithLineMetricsGuides_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_Multiline_WithLineMetricsGuides_MatchesReference_Rgba32.png new file mode 100644 index 000000000..f1176f361 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_Multiline_WithLineMetricsGuides_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b77316ee5bc96af4106953c71180a1e16d8740e1a9d64011513e48d06b200af6 +size 26419 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_PenOnly_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_PenOnly_MatchesReference_Rgba32.png new file mode 100644 index 000000000..830aad8ab --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_PenOnly_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de3bd81004a0454d382aaaf21b74adee9114a3427c8db693f952e266da1511b6 +size 3211 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_WithWrappingAlignmentAndLineSpacing_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_WithWrappingAlignmentAndLineSpacing_MatchesReference_Rgba32.png new file mode 100644 index 000000000..c03a6cf88 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/DrawText_WithWrappingAlignmentAndLineSpacing_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:616d67dcd42fa592004096d7f76fd2248875018c1d21c2502e56f18d32b33e52 +size 45728 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_NormalizeOutputFalse_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_NormalizeOutputFalse_MatchesReference_Rgba32.png new file mode 100644 index 000000000..c17c63c2c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_NormalizeOutputFalse_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06c8db662ea35a092d6dc2e8026a3ee6a0ae7d965406a64b3ae9dd3054a81031 +size 3358 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_PathBuilder_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_PathBuilder_MatchesReference_Rgba32.png new file mode 100644 index 000000000..f318c18d7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_PathBuilder_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ab4c143dc8dc98359abce0e1a89cbd77a6224fe4dab44a35a4164222db365d4 +size 3439 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_WithPatternAndGradientPens_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_WithPatternAndGradientPens_MatchesReference_Rgba32.png new file mode 100644 index 000000000..c5fbb315f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Draw_WithPatternAndGradientPens_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfca64f26265f02d1912c943b09c47a956d8f7cf1b57b64dbdcc0175d25610c7 +size 11611 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_PathBuilder_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_PathBuilder_MatchesReference_Rgba32.png new file mode 100644 index 000000000..e3b66f93d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_PathBuilder_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026cd69f6f77bec21f60fa76091a1afa6278beedf0a360288bb92269beaba113 +size 2682 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_SelfIntersectingPath_EvenOddVsNonZero_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_SelfIntersectingPath_EvenOddVsNonZero_MatchesReference_Rgba32.png new file mode 100644 index 000000000..7c1ab4ae4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_SelfIntersectingPath_EvenOddVsNonZero_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c63a3f5fc2abf1879f891590f5fd2d85be120f3109d1cf46a264b64e6ac8e7f +size 8188 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_WithGradientAndPatternBrushes_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_WithGradientAndPatternBrushes_MatchesReference_Rgba32.png new file mode 100644 index 000000000..ed8d3fa3d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Fill_WithGradientAndPatternBrushes_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8efbb68954ec0d4e64a9e502282aeabea32c03310d53f9ceaa245153ff8f2642 +size 18940 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_NoCpuFrame_WithReadbackCapability_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_NoCpuFrame_WithReadbackCapability_MatchesReference_Rgba32.png new file mode 100644 index 000000000..11493e17c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_NoCpuFrame_WithReadbackCapability_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c2b561014aa035424a8745a1c20de3ce33efb5d11137b065a9ec43eed279dd8 +size 12884 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_PathBuilder_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_PathBuilder_MatchesReference_Rgba32.png new file mode 100644 index 000000000..1406aa4a3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_PathBuilder_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8ca107a5968ff2c14ecc4c3e6264123cf25148074734360552bb6ca13d08a8b +size 12904 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_Path_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_Path_MatchesReference_Rgba32.png new file mode 100644 index 000000000..11493e17c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/Process_Path_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c2b561014aa035424a8745a1c20de3ce33efb5d11137b065a9ec43eed279dd8 +size 12884 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/RestoreTo_MultipleStates_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/RestoreTo_MultipleStates_MatchesReference_Rgba32.png new file mode 100644 index 000000000..d2c44a3c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/RestoreTo_MultipleStates_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f08dfc8106c343c0e2af1e76caccf0fbb1dd01f9940b7f8fcc67679ae47161c5 +size 4881 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/SaveRestore_ClipPath_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/SaveRestore_ClipPath_MatchesReference_Rgba32.png new file mode 100644 index 000000000..51474a423 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/SaveRestore_ClipPath_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:568e53bd59a24331d0aa6c07772819e1c3482f8826d7e373650dab548d8abb55 +size 1332 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/TextMeasuring_RenderedMetrics_MatchesReference_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/TextMeasuring_RenderedMetrics_MatchesReference_Rgba32.png new file mode 100644 index 000000000..7d7de4506 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/DrawingCanvasTests/TextMeasuring_RenderedMetrics_MatchesReference_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6b5966124275a574cc12f09f333a60e3a9c72e68a4e4304908dc00bf8e7680f +size 27575 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(10).png b/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(10).png deleted file mode 100644 index 652850f5d..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(10).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a837b1b94ddc2813b0feaeffabc22c90df4bd4fdaf282c229241b0316e5621b7 -size 77807 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(3).png b/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(3).png deleted file mode 100644 index c622d0bfd..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(3).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a282acfa163f23bd7e1a6d97c174ff290afb3edbf6b8a6f65dbcca2b7e0fa8c -size 16748 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(5).png b/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(5).png deleted file mode 100644 index 646002b00..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_LinesScaled_Scale(5).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:68cfa2c39e498a8c147a9fe5ca4dff10d3b53a5a5ce23bfdd3e7b7915fcff8cf -size 32709 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_Lines_PixelOffset(0).png b/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_Lines_PixelOffset(0).png deleted file mode 100644 index a3d1fd999..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_Lines_PixelOffset(0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cab703fe17ffd19264e0ca155945aa7c1d0bc4c6317bc87e6d64e513368e0f85 -size 4429 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_Lines_PixelOffset(5500).png b/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_Lines_PixelOffset(5500).png deleted file mode 100644 index 4431a489a..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_Mississippi_Lines_PixelOffset(5500).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7eaef6cc66cd48c391fda1775da6594728de9f16cf0b9a4718ce312841624f73 -size 40967 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_States_Fill.png b/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_States_Fill.png deleted file mode 100644 index 6ea570a90..000000000 --- a/tests/Images/ReferenceOutput/Drawing/DrawingRobustnessTests/LargeGeoJson_States_Fill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:85f9dc073233b4703db8ab4df049de3d551912104863bf89756141c61667083a -size 386553 diff --git a/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill.png b/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill.png deleted file mode 100644 index 8ad0ef2cf..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd3f480017119bc70192255dc3315f444747d1379bec915e7cc3dd771961cecf -size 2410 diff --git a/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill__Overlap.png b/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill__Overlap.png deleted file mode 100644 index 689851571..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill__Overlap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b23186fee34f325c965e4a7dc623caa6e71527b05aafa6089ed0e477993e4f6 -size 2656 diff --git a/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill__Transparent.png b/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill__Transparent.png deleted file mode 100644 index 48f1ff7af..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillComplexPolygonTests/ComplexPolygon_SolidFill__Transparent.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b970c12a4d204c04b54b9907d7206c5e3036a04e40cc6ef87e986509faa8fa4a -size 2376 diff --git a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawLandscapeImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawLandscapeImage_Rgba32.png deleted file mode 100644 index ce3f363d5..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawLandscapeImage_Rgba32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f79a9486aee6b3201ba20876028c25b1251e70996af8e4ee4847ac294e87458b -size 59884 diff --git a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawNegativeOffsetImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawNegativeOffsetImage_Rgba32.png deleted file mode 100644 index 3a4538c89..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawNegativeOffsetImage_Rgba32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2cad09068867e5c56874dd5b44937fd22a386d27ff82e6c5d974444512f1950a -size 100398 diff --git a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawOffsetImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawOffsetImage_Rgba32.png deleted file mode 100644 index 71a0bc8ff..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawOffsetImage_Rgba32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20c4f6324712fcc2e6b6cf012c169290a01a9199eb96ba3691550b75b2b2b524 -size 150296 diff --git a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawPortraitImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawPortraitImage_Rgba32.png deleted file mode 100644 index a0fe867a5..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanDrawPortraitImage_Rgba32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb1807440c0a8abb05eb332b379dabff4b1be83f804cc3e2e65978a758373940 -size 48551 diff --git a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanOffsetImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanOffsetImage_Rgba32.png deleted file mode 100644 index 5acd7f8fe..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanOffsetImage_Rgba32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b3e455c552537815ca1d5c0699b5fa36bd1963a0a28a06c8cfcf5fb8c5c884f -size 251984 diff --git a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanOffsetViaBrushImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanOffsetViaBrushImage_Rgba32.png deleted file mode 100644 index be90717e5..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/CanOffsetViaBrushImage_Rgba32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2fd4fd80d6bfb522b21d884fc6aba6bb5049d5feeb5a05c8b86e087a7229a440 -size 299061 diff --git a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/UseBrushOfDifferentPixelType_Bgra32.png b/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/UseBrushOfDifferentPixelType_Bgra32.png deleted file mode 100644 index c0ce4bad1..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/UseBrushOfDifferentPixelType_Bgra32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/UseBrushOfDifferentPixelType_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/UseBrushOfDifferentPixelType_Rgba32.png deleted file mode 100644 index c0ce4bad1..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillImageBrushTests/UseBrushOfDifferentPixelType_Rgba32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-20).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-20).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-20).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-49).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-49).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-49).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-50).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-50).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-50).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-60).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-60).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_-60).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_0).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_0).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-110_0).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-99_0).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-99_0).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(-99_0).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-50).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-50).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-50).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-60).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-60).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-60).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-20).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-20).png deleted file mode 100644 index 58e64a8ae..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-20).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e3ab90923f0e249b6b2f542311ae57ef9921483f8f934e4d57437654abee240 -size 357 diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-49).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-49).png deleted file mode 100644 index 04782a9bf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-49).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-50).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-50).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-50).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-60).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-60).png deleted file mode 100644 index 06e28c378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_-60).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_0).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_0).png deleted file mode 100644 index cef683600..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(110_0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15541d241c3e6d1d47388544731bd7c0da4d5533919dc791786193473ce788f1 -size 452 diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(99_0).png b/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(99_0).png deleted file mode 100644 index b9df1a69b..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(99_0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6aeb2dd8e99354cbd41360611642e9b286e646382dc224c5f9ce487aef5beba -size 483 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPathTests/FillPathCanvasArcs.png b/tests/Images/ReferenceOutput/Drawing/FillPathTests/FillPathCanvasArcs.png deleted file mode 100644 index 2180c82a6..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPathTests/FillPathCanvasArcs.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d096fcea8556aaf91eea17a31896527f10285c5d17e073dbe2715aadfa3bdcd5 -size 1500 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPathTests/FillPathSVGArcs.png b/tests/Images/ReferenceOutput/Drawing/FillPathTests/FillPathSVGArcs.png deleted file mode 100644 index 8d8db722d..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPathTests/FillPathSVGArcs.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:94d1f65c198c0e57405459392ffa2b6c36d64f7fe4053960216eb487bc4ed0fa -size 2607 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal.png deleted file mode 100644 index a7ca5cb2a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal_Transparent.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal_Transparent.png deleted file mode 100644 index db76bec8a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal_Transparent.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal_Transparentx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal_Transparentx4.png deleted file mode 100644 index c3bd2318e..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonal_Transparentx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonalx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonalx4.png deleted file mode 100644 index a1ed06f0b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/BackwardDiagonalx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal.png deleted file mode 100644 index c5aead13b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal_Transparent.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal_Transparent.png deleted file mode 100644 index f5a69702f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal_Transparent.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal_Transparentx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal_Transparentx4.png deleted file mode 100644 index d5e78ba98..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonal_Transparentx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonalx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonalx4.png deleted file mode 100644 index 274f72b11..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/ForwardDiagonalx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal.png deleted file mode 100644 index 482af03e7..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal_Transparent.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal_Transparent.png deleted file mode 100644 index 4dcdf0038..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal_Transparent.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal_Transparentx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal_Transparentx4.png deleted file mode 100644 index 76052830b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontal_Transparentx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontalx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontalx4.png deleted file mode 100644 index 1fa5a8973..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Horizontalx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min.png deleted file mode 100644 index 79cf04240..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min_Transparent.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min_Transparent.png deleted file mode 100644 index 1429926cb..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min_Transparent.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min_Transparentx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min_Transparentx4.png deleted file mode 100644 index b17dfaaa2..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Min_Transparentx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Minx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Minx4.png deleted file mode 100644 index 93788ac1a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Minx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10.png deleted file mode 100644 index 46e10edf6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10_Transparent.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10_Transparent.png deleted file mode 100644 index c6d2a3132..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10_Transparent.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10_Transparentx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10_Transparentx4.png deleted file mode 100644 index feff17b26..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10_Transparentx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10x4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10x4.png deleted file mode 100644 index 02efd3930..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent10x4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20.png deleted file mode 100644 index b508c01ad..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20_Transparent.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20_Transparent.png deleted file mode 100644 index 3522fd2ab..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20_Transparent.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20_Transparentx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20_Transparentx4.png deleted file mode 100644 index 47614322e..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20_Transparentx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20x4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20x4.png deleted file mode 100644 index 24d01eb94..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Percent20x4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical.png deleted file mode 100644 index 6f5d24ff9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical_Transparent.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical_Transparent.png deleted file mode 100644 index 9f1f581ed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical_Transparent.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical_Transparentx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical_Transparentx4.png deleted file mode 100644 index 98c595586..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Vertical_Transparentx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Verticalx4.png b/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Verticalx4.png deleted file mode 100644 index 22b211029..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPatternBrushTests/Verticalx4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(EvenOdd).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(EvenOdd).png deleted file mode 100644 index 36bc63aa0..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(EvenOdd).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c1275eca572b8121e7f1f3a2f0ebee9684c0f12e85ad32dd9ab11aa1c8bfc9d -size 241 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(Nonzero).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(Nonzero).png deleted file mode 100644 index 02818fe19..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(Nonzero).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Concave_Reverse(False).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Concave_Reverse(False).png deleted file mode 100644 index ff75d7684..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Concave_Reverse(False).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Concave_Reverse(True).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Concave_Reverse(True).png deleted file mode 100644 index 7b75147f4..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Concave_Reverse(True).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rect_Rgba32_Car_rect.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rect_Rgba32_Car_rect.png deleted file mode 100644 index 317d43265..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rect_Rgba32_Car_rect.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:39252d1bc31ac8ffca3e4975f87a8d15197a47e17506f5c4c857a6327db011ca -size 38416 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rect_Rgba32_ducky_rect.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rect_Rgba32_ducky_rect.png deleted file mode 100644 index 0945fc432..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rect_Rgba32_ducky_rect.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4721c27c827c3f716ad18ae1cafc32132ae47b6557a01d4c16acaa7b7d92400b -size 20601 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rgba32_Car.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rgba32_Car.png deleted file mode 100644 index 41994df52..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rgba32_Car.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:662e4f78996d9c12d6698410543c6ef0aa474337b2d03084924b1c706a1e4916 -size 13462 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rgba32_ducky.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rgba32_ducky.png deleted file mode 100644 index d24bd6b86..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_ImageBrush_Rgba32_ducky.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:24f74782275e63f241431410af079089f2bd229ab06071a472de1a360ca934d7 -size 17093 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Pattern_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Pattern_Rgba32.png deleted file mode 100644 index ad5b15cb6..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Pattern_Rgba32.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b08fd85ee90ab448f31ba0eb686142e93cd8d80e0cc6b8abb3a9f615bd5e5cb -size 1687 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Bgr24_Yellow_A1.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Bgr24_Yellow_A1.png deleted file mode 100644 index 12ecc129c..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Bgr24_Yellow_A1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b81ef6b2a5b4f848d740f1ff9de08c90c0dc1e7ef3d11b43c2b704b29546c26d -size 2806 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Rgba32_White_A0.6.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Rgba32_White_A0.6.png deleted file mode 100644 index d6c24cbf4..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Rgba32_White_A0.6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:145da3c382448615cf0b90f4718b8a79a8b2d7f3dd33042c755e15d5b127d33b -size 2788 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Rgba32_White_A1.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Rgba32_White_A1.png deleted file mode 100644 index 7c67a4ece..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Rgba32_White_A1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:779a0d18611eb44bead0f9dbd704c3c9e10fbf92e906fdd4281075f3d5c946f9 -size 2906 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Transformed_Rgba32_BasicTestPattern250x350.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Transformed_Rgba32_BasicTestPattern250x350.png deleted file mode 100644 index 5be0df234..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Transformed_Rgba32_BasicTestPattern250x350.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac469abbc75f28cfb40ff8dc84879c6a5a92b0259ae87cd475e3c2df48b2cbbd -size 5421 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle.png deleted file mode 100644 index 52e6c32c0..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa159948e580ca2b8894f05458142eeed7fbfb585eafd6f88f0f1fb035d9cbc1 -size 926 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Difference.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Difference.png deleted file mode 100644 index 417ff62ad..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Difference.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6064686c9b97d19add654ec6807da554f308d34e7d3d22a24b68d7f4590500c -size 2840 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Intersection.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Intersection.png deleted file mode 100644 index 32da0db02..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Intersection.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5a7a0a64ff4d9c0d68044155aab68f4cb008b094d95633e55332ef9f0ca40ae -size 2955 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_None.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_None.png deleted file mode 100644 index 8a7568536..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_None.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:31e1a9ddb69387f586bafd7d53a75df46103b3f724649b69ab24259cbe8df148 -size 2094 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Union.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Union.png deleted file mode 100644 index cb8bd9b88..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Union.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc15adf797a7c22b101eb70d0d31fc8a34767db3941c6aace5cf66b3064fb15e -size 1539 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Xor.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Xor.png deleted file mode 100644 index bab0d07ba..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_StarCircle_AllOperations_Xor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f5dd907efe582b038f0a1e74cb006d2e803c0feb2063ee49056625098e6430d -size 2832 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(True)_IntersectionRule(EvenOdd).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(True)_IntersectionRule(EvenOdd).png deleted file mode 100644 index 85bc516d9..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(True)_IntersectionRule(EvenOdd).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b122fcb6c408c714a98f85fc0128179caf2de869c4dbbcd0211bba500e61e2cd -size 2648 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(True)_IntersectionRule(Nonzero).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(True)_IntersectionRule(Nonzero).png deleted file mode 100644 index 85bc516d9..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(True)_IntersectionRule(Nonzero).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b122fcb6c408c714a98f85fc0128179caf2de869c4dbbcd0211bba500e61e2cd -size 2648 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_IntersectionRules_Nonzero_Rgba32_Solid60x60_(0,0,255,255).bmp b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_IntersectionRules_Nonzero_Rgba32_Solid60x60_(0,0,255,255).bmp deleted file mode 100644 index f4917262c..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_IntersectionRules_Nonzero_Rgba32_Solid60x60_(0,0,255,255).bmp and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_IntersectionRules_OddEven_Rgba32_Solid60x60_(0,0,255,255).bmp b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_IntersectionRules_OddEven_Rgba32_Solid60x60_(0,0,255,255).bmp deleted file mode 100644 index 7c1f28570..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_IntersectionRules_OddEven_Rgba32_Solid60x60_(0,0,255,255).bmp and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RectangularPolygon_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RectangularPolygon_Rgba32.png deleted file mode 100644 index f42b72e7a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RectangularPolygon_Rgba32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(50)_Ang(0).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(50)_Ang(0).png deleted file mode 100644 index 15431f30a..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(50)_Ang(0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02891cbdc2242395343290bc5403fd161fab49e470f93fd0c5639a93464f274b -size 1754 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(60)_Ang(-180).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(60)_Ang(-180).png deleted file mode 100644 index 4e29cc25b..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(60)_Ang(-180).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f95be339c0fc7f9315968001722777d1eebddbf6eea41c8d9d524b8775842763 -size 2024 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(60)_Ang(20).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(60)_Ang(20).png deleted file mode 100644 index 3fe215ed7..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(3)_R(60)_Ang(20).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86280439d207ed0a74595757af265a313d75f7ff8b7502eb17d2d7184855eb12 -size 2499 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(5)_R(70)_Ang(0).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(5)_R(70)_Ang(0).png deleted file mode 100644 index 8ad422f6d..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(5)_R(70)_Ang(0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d762246aeec860558a8ee8e5318126937be11a9561a4bd1ff18f90900858bc2b -size 2852 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(7)_R(80)_Ang(-180).png b/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(7)_R(80)_Ang(-180).png deleted file mode 100644 index c7cb00188..000000000 --- a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RegularPolygon_V(7)_R(80)_Ang(-180).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20457c79f2f5a782088bc4f9a333d0092ba9c5f5307835cdfc3ca7bf527420e4 -size 3247 diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png deleted file mode 100644 index 57cb2e6da..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png deleted file mode 100644 index 346ea8b42..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png deleted file mode 100644 index 2ffa1e7e9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png deleted file mode 100644 index 57cb2e6da..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png deleted file mode 100644 index e93fd5a60..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png deleted file mode 100644 index 2614a2f11..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png deleted file mode 100644 index df75d8d0d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png deleted file mode 100644 index 81e6bb8d8..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png deleted file mode 100644 index 938514381..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png deleted file mode 100644 index db67d9ddd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png deleted file mode 100644 index b428583f3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png deleted file mode 100644 index db67d9ddd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png deleted file mode 100644 index 57cb2e6da..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png deleted file mode 100644 index 346ea8b42..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png deleted file mode 100644 index 2ffa1e7e9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png deleted file mode 100644 index 57cb2e6da..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png deleted file mode 100644 index e93fd5a60..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png deleted file mode 100644 index 2614a2f11..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png deleted file mode 100644 index df75d8d0d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png deleted file mode 100644 index 81e6bb8d8..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png deleted file mode 100644 index 938514381..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png deleted file mode 100644 index db67d9ddd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png deleted file mode 100644 index b428583f3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png deleted file mode 100644 index db67d9ddd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_Argb32.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_Argb32.png deleted file mode 100644 index 1410827bc..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_Argb32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png deleted file mode 100644 index 1410827bc..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_RgbaVector.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_RgbaVector.png deleted file mode 100644 index 1410827bc..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSinglePixelType_RgbaVector.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank16x7.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank16x7.png deleted file mode 100644 index e20907b99..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank16x7.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank1x1.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank1x1.png deleted file mode 100644 index 4e4ee1ee1..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank1x1.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank33x32.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank33x32.png deleted file mode 100644 index 31965cc3a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank33x32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank400x500.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank400x500.png deleted file mode 100644 index f3c6b080b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank400x500.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank7x4.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank7x4.png deleted file mode 100644 index 8914b9c49..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/DoesNotDependOnSize_Blank7x4.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png deleted file mode 100644 index 4fdb95635..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png deleted file mode 100644 index b56cd2f34..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png deleted file mode 100644 index 4fdb95635..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png deleted file mode 100644 index b56cd2f34..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/FillRegion_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Blue.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Blue.png deleted file mode 100644 index c5ad73f42..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Blue.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Khaki.png b/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Khaki.png deleted file mode 100644 index 4860936c1..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/FillSolidBrushTests/WhenColorIsOpaque_OverridePreviousColor_Khaki.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.10.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.10.png deleted file mode 100644 index fa2315d73..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.10.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.40.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.40.png deleted file mode 100644 index b980bbfe9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.40.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.80.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.80.png deleted file mode 100644 index 9a3758c7b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_0.80.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.00.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.00.png deleted file mode 100644 index 8b93c88ba..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.00.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.20.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.20.png deleted file mode 100644 index 7318e2365..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.20.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.60.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.60.png deleted file mode 100644 index 41683430e..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_1.60.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_2.00.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_2.00.png deleted file mode 100644 index 8f88c760a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/AxisParallelEllipsesWithDifferentRatio_2.00.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_00deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_00deg.png deleted file mode 100644 index fa2315d73..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_00deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_30deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_30deg.png deleted file mode 100644 index cf2981f60..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_30deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_45deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_45deg.png deleted file mode 100644 index cae5c978f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_45deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_90deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_90deg.png deleted file mode 100644 index 0f315979d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.10_AT_90deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_00deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_00deg.png deleted file mode 100644 index b980bbfe9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_00deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_30deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_30deg.png deleted file mode 100644 index aa53fe650..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_30deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_45deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_45deg.png deleted file mode 100644 index e5c294a42..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_45deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_90deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_90deg.png deleted file mode 100644 index 39a82ea75..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.40_AT_90deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_00deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_00deg.png deleted file mode 100644 index 9a3758c7b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_00deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_30deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_30deg.png deleted file mode 100644 index ec42d87b3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_30deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_45deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_45deg.png deleted file mode 100644 index bac85109c..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_45deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_90deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_90deg.png deleted file mode 100644 index 9250a255a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_0.80_AT_90deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_00deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_00deg.png deleted file mode 100644 index 8b93c88ba..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_00deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_30deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_30deg.png deleted file mode 100644 index 0b26dc5be..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_30deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_45deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_45deg.png deleted file mode 100644 index 83299be4f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_45deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_90deg.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_90deg.png deleted file mode 100644 index 8b93c88ba..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/RotatedEllipsesWithDifferentRatio_1.00_AT_90deg.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png deleted file mode 100644 index fe59554e5..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillEllipticGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;000080FF@0.2;90EE90FF@0.5;90EE90FF@0.9;].png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;000080FF@0.2;90EE90FF@0.5;90EE90FF@0.9;].png deleted file mode 100644 index 372948bac..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;000080FF@0.2;90EE90FF@0.5;90EE90FF@0.9;].png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;90EE90FF@0.5;FF0000FF@1;].png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;90EE90FF@0.5;FF0000FF@1;].png deleted file mode 100644 index 87ee84ba5..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;90EE90FF@0.5;FF0000FF@1;].png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,499)_TO_(499,0)__[000080FF@0;90EE90FF@0.2;FFFF00FF@0.5;FF0000FF@0.9;].png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,499)_TO_(499,0)__[000080FF@0;90EE90FF@0.2;FFFF00FF@0.5;FF0000FF@0.9;].png deleted file mode 100644 index 42bc4d9fd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(0,499)_TO_(499,0)__[000080FF@0;90EE90FF@0.2;FFFF00FF@0.5;FF0000FF@0.9;].png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(499,499)_TO_(0,0)__[000080FF@0;90EE90FF@0.7;FFFF00FF@0.8;000080FF@0.9;].png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(499,499)_TO_(0,0)__[000080FF@0;90EE90FF@0.7;FFFF00FF@0.8;000080FF@0.9;].png deleted file mode 100644 index fc5c6ed98..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/ArbitraryGradients_(499,499)_TO_(0,0)__[000080FF@0;90EE90FF@0.7;FFFF00FF@0.8;000080FF@0.9;].png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/BrushApplicatorIsThreadSafeIssue1044.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/BrushApplicatorIsThreadSafeIssue1044.png deleted file mode 100644 index 0f6b4e174..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/BrushApplicatorIsThreadSafeIssue1044.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_BottomLeft.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_BottomLeft.png deleted file mode 100644 index 9d87a3e8b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_BottomLeft.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_BottomRight.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_BottomRight.png deleted file mode 100644 index c990c85f4..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_BottomRight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_TopLeft.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_TopLeft.png deleted file mode 100644 index 7b4f2575a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_TopLeft.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_TopRight.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_TopRight.png deleted file mode 100644 index 88fbc2377..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DiagonalReturnsCorrectImages_TopRight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Argb32.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Argb32.png deleted file mode 100644 index e52fe3a25..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Argb32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Rgb24.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Rgb24.png deleted file mode 100644 index e52fe3a25..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Rgb24.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png deleted file mode 100644 index e52fe3a25..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/DoesNotDependOnSinglePixelType_Rgba32.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_DontFill.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_DontFill.png deleted file mode 100644 index 993d56068..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_DontFill.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_None.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_None.png deleted file mode 100644 index 61c620618..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_None.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_Reflect.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_Reflect.png deleted file mode 100644 index fb9b58378..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_Reflect.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_Repeat.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_Repeat.png deleted file mode 100644 index 22bdf1d3c..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalGradientWithRepMode_Repeat.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalReturnsUnicolorColumns.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalReturnsUnicolorColumns.png deleted file mode 100644 index 55ab3b4c4..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/HorizontalReturnsUnicolorColumns.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/MultiplePointGradients_(0,0)_TO_(199,199)__[000000FF@0;0000FFFF@0.25;FF0000FF@0.5;FFFFFFFF@0.75;00FF00FF@1;].png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/MultiplePointGradients_(0,0)_TO_(199,199)__[000000FF@0;0000FFFF@0.25;FF0000FF@0.5;FFFFFFFF@0.75;00FF00FF@1;].png deleted file mode 100644 index fea93e74d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/MultiplePointGradients_(0,0)_TO_(199,199)__[000000FF@0;0000FFFF@0.25;FF0000FF@0.5;FFFFFFFF@0.75;00FF00FF@1;].png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/VerticalBrushReturnsUnicolorRows.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/VerticalBrushReturnsUnicolorRows.png deleted file mode 100644 index 843833d04..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/VerticalBrushReturnsUnicolorRows.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.1_0.3_0.6.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.1_0.3_0.6.png deleted file mode 100644 index 91a2e5ba2..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.1_0.3_0.6.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.2_0.4_0.6_0.8.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.2_0.4_0.6_0.8.png deleted file mode 100644 index 104e80284..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.2_0.4_0.6_0.8.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.5.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.5.png deleted file mode 100644 index 2d6c2c25e..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithDoubledStopsProduceDashedPatterns_0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png deleted file mode 100644 index fe59554e5..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillComplex.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillComplex.png deleted file mode 100644 index 145fba142..000000000 --- a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillComplex.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73aba67441d29001460d8fab86ba1bc5623ec1196424cb7f30a0e074cdc52525 -size 9396 diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillRectangleWithDifferentColors.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillRectangleWithDifferentColors.png deleted file mode 100644 index 4f0b8eebe..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillRectangleWithDifferentColors.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillRectangleWithDifferentColors_Rgba32_Blank10x10.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillRectangleWithDifferentColors_Rgba32_Blank10x10.png deleted file mode 100644 index 4f0b8eebe..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillRectangleWithDifferentColors_Rgba32_Blank10x10.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColors.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColors.png deleted file mode 100644 index edfa65ddd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColors.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColorsCenter.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColorsCenter.png deleted file mode 100644 index 7069f73dd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColorsCenter.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColorsCenter_Rgba32_Blank20x20.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColorsCenter_Rgba32_Blank20x20.png deleted file mode 100644 index 7069f73dd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColorsCenter_Rgba32_Blank20x20.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColors_Rgba32_Blank20x20.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColors_Rgba32_Blank20x20.png deleted file mode 100644 index edfa65ddd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithDifferentColors_Rgba32_Blank20x20.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithGreyscale.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithGreyscale.png deleted file mode 100644 index 6b6619a51..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithGreyscale.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithGreyscale_HalfSingle_Blank20x20.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithGreyscale_HalfSingle_Blank20x20.png deleted file mode 100644 index 6b6619a51..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillTriangleWithGreyscale_HalfSingle_Blank20x20.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillWithCustomCenterColor.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillWithCustomCenterColor.png deleted file mode 100644 index 68f88d380..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillWithCustomCenterColor.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillWithCustomCenterColor_Rgba32_Blank10x10.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillWithCustomCenterColor_Rgba32_Blank10x10.png deleted file mode 100644 index 2f4392464..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/FillWithCustomCenterColor_Rgba32_Blank10x10.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/ShouldRotateTheColorsWhenThereAreMorePoints.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/ShouldRotateTheColorsWhenThereAreMorePoints.png deleted file mode 100644 index 87515e696..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/ShouldRotateTheColorsWhenThereAreMorePoints.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/ShouldRotateTheColorsWhenThereAreMorePoints_Rgba32_Blank10x10.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/ShouldRotateTheColorsWhenThereAreMorePoints_Rgba32_Blank10x10.png deleted file mode 100644 index 87515e696..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillPathGradientBrushTests/ShouldRotateTheColorsWhenThereAreMorePoints_Rgba32_Blank10x10.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(-40,100).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(-40,100).png deleted file mode 100644 index 1ed0a00e7..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(-40,100).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(0,0).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(0,0).png deleted file mode 100644 index a9a508e61..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(0,0).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(0,100).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(0,100).png deleted file mode 100644 index e21877e1f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(0,100).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(100,0).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(100,0).png deleted file mode 100644 index a181fff1e..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(100,0).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(100,100).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(100,100).png deleted file mode 100644 index 3a2515a1e..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithDifferentCentersReturnsImage_center(100,100).png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png deleted file mode 100644 index d2e3b3ebb..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillRadialGradientBrushTests/WithEqualColorsReturnsUnicolorImage.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(0,end360).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(0,end360).png deleted file mode 100644 index 718c00be0..000000000 --- a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(0,end360).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f06ea31a467d5e59e0622c3834dd962f42f328cdcc8a253fcadbd38c6ea21e5 -size 10623 diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(180,end540).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(180,end540).png deleted file mode 100644 index 41a7c333b..000000000 --- a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(180,end540).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99d1013b8b1173c253532ea299ff7bbb13aee0d6bea688cb30edf989359dc2af -size 10732 diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(270,end630).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(270,end630).png deleted file mode 100644 index 4de5fe103..000000000 --- a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(270,end630).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b51e8f78f25855e033b0be07ac4568617ce4c3c6a09df03e1ca5105df35f3b53 -size 10685 diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(90,end450).png b/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(90,end450).png deleted file mode 100644 index 6edeb18dd..000000000 --- a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillSweepGradientBrushTests/SweepGradientBrush_RendersFullSweep_Every90Degrees_start(90,end450).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:733a35be9abf41327757907092634424f0901b42a830daad4fb7959b50d129e2 -size 10523 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Add.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Darken.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-HardLight.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Lighten.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Multiply.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Normal.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Overlay.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Screen.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Subtract.png new file mode 100644 index 000000000..1c35d0541 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Clear_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4c37fafab2ab35b0307bf336a8b303be9b9f5799ebf82a098fa61fecb30b2aa +size 115 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Add.png new file mode 100644 index 000000000..0f1587481 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fed923205689afec9dee09f05dcad85a8a0ed7f074a0156fb09f1b0db0f9d97 +size 1284 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Darken.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-HardLight.png new file mode 100644 index 000000000..ca9a13ab4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3ec1e24fa198c5440c3ff374c3033a80438cfbbf59cf00255c4e3f3c21f5095 +size 1283 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Lighten.png new file mode 100644 index 000000000..0f1587481 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fed923205689afec9dee09f05dcad85a8a0ed7f074a0156fb09f1b0db0f9d97 +size 1284 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Multiply.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Normal.png new file mode 100644 index 000000000..0f1587481 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fed923205689afec9dee09f05dcad85a8a0ed7f074a0156fb09f1b0db0f9d97 +size 1284 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Overlay.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Screen.png new file mode 100644 index 000000000..0f1587481 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fed923205689afec9dee09f05dcad85a8a0ed7f074a0156fb09f1b0db0f9d97 +size 1284 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Subtract.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestAtop_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Add.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Darken.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-HardLight.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Lighten.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Multiply.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Normal.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Overlay.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Screen.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Subtract.png new file mode 100644 index 000000000..b794bca24 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestIn_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80057da1abe172d84c830c314dfd93594e2d2671cf1d65ee2f235ad42a15ffe7 +size 816 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Add.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Darken.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-HardLight.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Lighten.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Multiply.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Normal.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Overlay.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Screen.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Subtract.png new file mode 100644 index 000000000..8c0d1942a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOut_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9262a38a082952e1f4ccf197c24f50eca407374d8ae0a9d94744217c7db8087 +size 981 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Add.png new file mode 100644 index 000000000..9083163df --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af96a68f392a22c4c24cb3e5bf52a38ab12d1eef7d6d74796eb51bb0581b7602 +size 944 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Darken.png new file mode 100644 index 000000000..30e91831a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4987695e61cda63c354e491a16c785446acfa27423adb4689e5a96a22aae85 +size 1285 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-HardLight.png new file mode 100644 index 000000000..d1ac5ac4e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6417e4c95ade9ed30d48d52ce3cc02967017aaf9d098fc954c02f40481c7a33f +size 1253 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Lighten.png new file mode 100644 index 000000000..9083163df --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af96a68f392a22c4c24cb3e5bf52a38ab12d1eef7d6d74796eb51bb0581b7602 +size 944 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Multiply.png new file mode 100644 index 000000000..30e91831a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4987695e61cda63c354e491a16c785446acfa27423adb4689e5a96a22aae85 +size 1285 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Normal.png new file mode 100644 index 000000000..9083163df --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af96a68f392a22c4c24cb3e5bf52a38ab12d1eef7d6d74796eb51bb0581b7602 +size 944 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Overlay.png new file mode 100644 index 000000000..30e91831a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4987695e61cda63c354e491a16c785446acfa27423adb4689e5a96a22aae85 +size 1285 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Screen.png new file mode 100644 index 000000000..9083163df --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af96a68f392a22c4c24cb3e5bf52a38ab12d1eef7d6d74796eb51bb0581b7602 +size 944 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Subtract.png new file mode 100644 index 000000000..30e91831a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-DestOver_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4987695e61cda63c354e491a16c785446acfa27423adb4689e5a96a22aae85 +size 1285 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Add.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Darken.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-HardLight.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Lighten.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Multiply.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Normal.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Overlay.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Screen.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Subtract.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Dest_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Add.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Darken.png new file mode 100644 index 000000000..f3ec6a941 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39a8142fe5fe2306cea381863fa10ca4074e7369f6d3230aa12682ceea1264c1 +size 972 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-HardLight.png new file mode 100644 index 000000000..f3ec6a941 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39a8142fe5fe2306cea381863fa10ca4074e7369f6d3230aa12682ceea1264c1 +size 972 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Lighten.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Multiply.png new file mode 100644 index 000000000..f3ec6a941 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39a8142fe5fe2306cea381863fa10ca4074e7369f6d3230aa12682ceea1264c1 +size 972 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Normal.png new file mode 100644 index 000000000..f3ec6a941 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39a8142fe5fe2306cea381863fa10ca4074e7369f6d3230aa12682ceea1264c1 +size 972 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Overlay.png new file mode 100644 index 000000000..10a1ae29c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ec23afb5c3cefe1028aabe892fbb192f1c5109852735024260c43379a855e25 +size 963 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Screen.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Subtract.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcAtop_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Add.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Darken.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-HardLight.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Lighten.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Multiply.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Normal.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Overlay.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Screen.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Subtract.png new file mode 100644 index 000000000..953f328ca --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcIn_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7977e99a1b19dad1ab36fc4265712f2e2c19aadce01d39a34b4e40b4386a02e5 +size 529 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Add.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Darken.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-HardLight.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Lighten.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Multiply.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Normal.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Overlay.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Screen.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Subtract.png new file mode 100644 index 000000000..79cd7f67a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOut_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664959de13475f003cf820c21d0ceb9cc7cb35537641810d684dbc0924e68e47 +size 896 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Add.png new file mode 100644 index 000000000..9083163df --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af96a68f392a22c4c24cb3e5bf52a38ab12d1eef7d6d74796eb51bb0581b7602 +size 944 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Darken.png new file mode 100644 index 000000000..30e91831a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4987695e61cda63c354e491a16c785446acfa27423adb4689e5a96a22aae85 +size 1285 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-HardLight.png new file mode 100644 index 000000000..30e91831a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4987695e61cda63c354e491a16c785446acfa27423adb4689e5a96a22aae85 +size 1285 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Lighten.png new file mode 100644 index 000000000..9083163df --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af96a68f392a22c4c24cb3e5bf52a38ab12d1eef7d6d74796eb51bb0581b7602 +size 944 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Multiply.png new file mode 100644 index 000000000..30e91831a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4987695e61cda63c354e491a16c785446acfa27423adb4689e5a96a22aae85 +size 1285 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Normal.png new file mode 100644 index 000000000..30e91831a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4987695e61cda63c354e491a16c785446acfa27423adb4689e5a96a22aae85 +size 1285 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Overlay.png new file mode 100644 index 000000000..d1ac5ac4e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6417e4c95ade9ed30d48d52ce3cc02967017aaf9d098fc954c02f40481c7a33f +size 1253 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Screen.png new file mode 100644 index 000000000..9083163df --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af96a68f392a22c4c24cb3e5bf52a38ab12d1eef7d6d74796eb51bb0581b7602 +size 944 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Subtract.png new file mode 100644 index 000000000..9083163df --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-SrcOver_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af96a68f392a22c4c24cb3e5bf52a38ab12d1eef7d6d74796eb51bb0581b7602 +size 944 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Add.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Darken.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-HardLight.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Lighten.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Multiply.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Normal.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Overlay.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Screen.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Subtract.png new file mode 100644 index 000000000..cb140a042 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Src_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5654c591507b8ffd7b9ec3b8a05fedcaf5c1ad000adf96bc78dbb12dacf0e55 +size 1110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Add.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Darken.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-HardLight.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Lighten.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Multiply.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Normal.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Overlay.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Screen.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Subtract.png new file mode 100644 index 000000000..3fca4d707 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendBlackEllipse_composition-Xor_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03cc34c4190df1db19fe2da5b79c00e190ddcd9f2cd44f8b35881101c0e8cd5 +size 1368 diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Clear_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Clear_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestAtop_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestAtop_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestIn_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestIn_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Add.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Darken.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-HardLight.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Lighten.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Multiply.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Normal.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Overlay.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Screen.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Subtract.png new file mode 100644 index 000000000..25b9ebb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOut_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939c8c6e12888b4d7cd9b97559b0d7a40b6a7f54f878d5c39a0fd5a0969eb14 +size 1004 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Add.png new file mode 100644 index 000000000..b878a6e43 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f063f49ab93cb981cf6191b5d03caf13e4dc09fd73da3236598f3ef54cff6c4 +size 2704 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Darken.png new file mode 100644 index 000000000..0c47c88ff --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bb8b4e83b35935e0ce500b0753f5666178057556b1222651826a9a8380eb348 +size 3012 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-HardLight.png new file mode 100644 index 000000000..c255b9188 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba1edb25eed3564259f027828c1e240bd20b69d5d9a9c4e6c74c94a9878e994c +size 3223 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Lighten.png new file mode 100644 index 000000000..cd1c71dd7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2904aa77ab96ba3460c577fb0c30ca782c9995f42a6ecbf56b744994dc3146e8 +size 2702 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Multiply.png new file mode 100644 index 000000000..5537e66d4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f248491c932a51679ff435d04a18db08353284ba17e18ce7fb63e3c9c38007e9 +size 3249 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Normal.png new file mode 100644 index 000000000..482e86051 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf887cd655d8f0fae2e2e77cc0bb2f06e472d26da21a2c85d1fc803114246a54 +size 2204 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Overlay.png new file mode 100644 index 000000000..797a55a9b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a0cb483159c337df967e4149686715b258e01ddb7f6594da03916aa64257364 +size 3311 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Screen.png new file mode 100644 index 000000000..9f3e8dd3b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34cacf851a38df21eb75b435216a235699a71da5a3489e9702a21d59e4d15563 +size 2707 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Subtract.png new file mode 100644 index 000000000..824249ecf --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-DestOver_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67d52bc2bbf96770536d1599f02dcd315a4235c1aaa6bdd523008e4c80240748 +size 3478 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Add.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Darken.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-HardLight.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Lighten.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Multiply.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Normal.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Overlay.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Screen.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Subtract.png new file mode 100644 index 000000000..95f4e5c4b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Dest_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:735711542ef4c1d22e35fa72e96c57e7fb024d20fab462b703898db6c7b3888c +size 126 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Add.png new file mode 100644 index 000000000..5bd706320 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6fcecae6f0cc30ad76eece9d90ea4120fb523f36048252e030e3ae26a72feaa +size 1081 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Darken.png new file mode 100644 index 000000000..724671eef --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31b60bf743e06c65fb1f4e80ce5fb1b96219378534a4d8f3186c7a15d71cc195 +size 1055 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-HardLight.png new file mode 100644 index 000000000..99589a939 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb33d5a5f24f60e182b3b5af37d1f3682d4ac7b6673ecbcaf30694ef45c84c2e +size 1393 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Lighten.png new file mode 100644 index 000000000..62033d730 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18da21ed099376868394db34d3f02fc6229278a4c1eca61d4400631b08415813 +size 1084 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Multiply.png new file mode 100644 index 000000000..091fc37a3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95977b134c222f40f06a4e3f5d287e3f4ea52c3d6d18b8ffccc6ec878ba9b02c +size 1305 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Normal.png new file mode 100644 index 000000000..d737910a0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5377bac8bac14130ee8ad758b929d9a09b5e7379551716fb1c8d10b842b6ec4 +size 1441 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Overlay.png new file mode 100644 index 000000000..a73417168 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a031832b3b3287402122f31c790e951f9f0e204a512f4282fb4efe37a5aa8e8d +size 1290 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Screen.png new file mode 100644 index 000000000..c908b8124 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edc855624619de74171b4aad783c8f51120c18579bd178d34c9a1f8ff8850ea8 +size 1084 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Subtract.png new file mode 100644 index 000000000..2754dfdb6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb1cd6693a9359d352b3991f0df2bfe13ce1b905b843fb22956646d7d0c541f4 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcIn_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcIn_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOut_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOut_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Add.png new file mode 100644 index 000000000..b878a6e43 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f063f49ab93cb981cf6191b5d03caf13e4dc09fd73da3236598f3ef54cff6c4 +size 2704 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Darken.png new file mode 100644 index 000000000..0c47c88ff --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bb8b4e83b35935e0ce500b0753f5666178057556b1222651826a9a8380eb348 +size 3012 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-HardLight.png new file mode 100644 index 000000000..797a55a9b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a0cb483159c337df967e4149686715b258e01ddb7f6594da03916aa64257364 +size 3311 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Lighten.png new file mode 100644 index 000000000..cd1c71dd7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2904aa77ab96ba3460c577fb0c30ca782c9995f42a6ecbf56b744994dc3146e8 +size 2702 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Multiply.png new file mode 100644 index 000000000..5537e66d4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f248491c932a51679ff435d04a18db08353284ba17e18ce7fb63e3c9c38007e9 +size 3249 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Normal.png new file mode 100644 index 000000000..dd9dbe563 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:950dfc002a89ff2bc96d0abe9b43b5573c3e60d0c0b3abe85035f286ba1e7af4 +size 3339 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Overlay.png new file mode 100644 index 000000000..c255b9188 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba1edb25eed3564259f027828c1e240bd20b69d5d9a9c4e6c74c94a9878e994c +size 3223 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Screen.png new file mode 100644 index 000000000..9f3e8dd3b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34cacf851a38df21eb75b435216a235699a71da5a3489e9702a21d59e4d15563 +size 2707 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Subtract.png new file mode 100644 index 000000000..a2661c8a3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-SrcOver_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:545ce1c6eec35fbdd16e70f8b02a6dbfeacd53d52f991e479e1da5dad706dc17 +size 2719 diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Src_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Src_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Add.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Darken.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-HardLight.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Lighten.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Multiply.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Normal.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Overlay.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Screen.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Subtract.png new file mode 100644 index 000000000..b0ecbb4d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendSemiTransparentRedEllipse_composition-Xor_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937f7b62b22acfc9c712a9a2e62f23307ffba52701e860d3c5af9188402fe437 +size 3214 diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Clear_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Clear_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestAtop_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestAtop_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestIn_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestIn_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Add.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Darken.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-HardLight.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Lighten.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Multiply.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Normal.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Overlay.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Screen.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Subtract.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOut_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Add.png new file mode 100644 index 000000000..f605fcda3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46a33f56439bd22e8bac8a53f76a4f6589f1838f2a606fb74b53902fae64952f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Darken.png new file mode 100644 index 000000000..49fcf0c57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b047a5333d017d4f872fc01474d6dd4e5be1ad4516a688bc519501cdfdf26cc5 +size 176 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-HardLight.png new file mode 100644 index 000000000..35b66a733 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b87e2dfb5166737c6e4d689b163937a948d993203b70b58b95334d0672ef997f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Lighten.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Multiply.png new file mode 100644 index 000000000..b5724b84b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:620336bdd922435ee42b83cb25691d9e762e889c9d051dc9248dea6a995d73bc +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Normal.png new file mode 100644 index 000000000..49fcf0c57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b047a5333d017d4f872fc01474d6dd4e5be1ad4516a688bc519501cdfdf26cc5 +size 176 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Overlay.png new file mode 100644 index 000000000..76673a63e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2afc2175477bf2822b6df58feaa87401875e61fc5ca58ece56f55411dfd1bf60 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Screen.png new file mode 100644 index 000000000..9d2e596e7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2927660c0739424f62be298ad2da0b3c06b69362c665e52f8efdb281dc6613fa +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Subtract.png new file mode 100644 index 000000000..1ab310b89 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-DestOver_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f23c0af3155c58de5594a423ac22f97288ad2d802c22f8ec89a9d32e9b363982 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Add.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Darken.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-HardLight.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Lighten.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Multiply.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Normal.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Overlay.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Screen.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Subtract.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Dest_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Add.png new file mode 100644 index 000000000..e55378a70 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b06af1e79bd0bd98b638bd2cd6cc80a25613723d8c7143abcce95e0b801b089d +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Darken.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-HardLight.png new file mode 100644 index 000000000..20e8f35a9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8d16b2057686a61d4be59dbffc052cc03c328c36fcff65ad5ad2b53529d0dbf +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Lighten.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Multiply.png new file mode 100644 index 000000000..9ab6cc778 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45283aa3e74d93b384df71feeac379a3911582fabdc0145e94a6eb488afe4d7b +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Normal.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Overlay.png new file mode 100644 index 000000000..2205dcba1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8500bb6fa35ef940302d9aa9d391077af78f8c509c93900f191e2e080b308d7f +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Screen.png new file mode 100644 index 000000000..1c55a5890 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdeac033f313e0151bd177678c697499f8f09c31df65d4e3a9b512f369c90da5 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Subtract.png new file mode 100644 index 000000000..adef2c3ae --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcAtop_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a26c4fb100ccb1026def8738856423e945cb193d8cd1c93365a08f4b3d6dad3 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcIn_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcIn_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOut_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOut_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Add.png new file mode 100644 index 000000000..f605fcda3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46a33f56439bd22e8bac8a53f76a4f6589f1838f2a606fb74b53902fae64952f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Darken.png new file mode 100644 index 000000000..49fcf0c57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b047a5333d017d4f872fc01474d6dd4e5be1ad4516a688bc519501cdfdf26cc5 +size 176 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-HardLight.png new file mode 100644 index 000000000..76673a63e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2afc2175477bf2822b6df58feaa87401875e61fc5ca58ece56f55411dfd1bf60 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Lighten.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Multiply.png new file mode 100644 index 000000000..b5724b84b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:620336bdd922435ee42b83cb25691d9e762e889c9d051dc9248dea6a995d73bc +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Normal.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Overlay.png new file mode 100644 index 000000000..35b66a733 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b87e2dfb5166737c6e4d689b163937a948d993203b70b58b95334d0672ef997f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Screen.png new file mode 100644 index 000000000..9d2e596e7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2927660c0739424f62be298ad2da0b3c06b69362c665e52f8efdb281dc6613fa +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Subtract.png new file mode 100644 index 000000000..2d42e2a0f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-SrcOver_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d378094f7ac82529a962ab155ec020c10c51394728f340bf20af17bf6e9bf07 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Add.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Add.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Add.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Darken.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Darken.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Darken.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-HardLight.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-HardLight.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-HardLight.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Lighten.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Lighten.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Lighten.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Multiply.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Multiply.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Multiply.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Normal.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Normal.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Normal.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Overlay.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Overlay.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Overlay.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Screen.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Screen.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Screen.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Subtract.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Src_blending-Subtract.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Src_blending-Subtract.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Add.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Darken.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-HardLight.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Lighten.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Multiply.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Normal.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Overlay.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Screen.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Subtract.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRectBlendTransparentEllipse_composition-Xor_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Add.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Darken.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-HardLight.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Lighten.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Multiply.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Normal.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Overlay.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Screen.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Subtract.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Clear_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Add.png new file mode 100644 index 000000000..f605fcda3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46a33f56439bd22e8bac8a53f76a4f6589f1838f2a606fb74b53902fae64952f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Darken.png new file mode 100644 index 000000000..49fcf0c57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b047a5333d017d4f872fc01474d6dd4e5be1ad4516a688bc519501cdfdf26cc5 +size 176 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-HardLight.png new file mode 100644 index 000000000..35b66a733 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b87e2dfb5166737c6e4d689b163937a948d993203b70b58b95334d0672ef997f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Lighten.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Multiply.png new file mode 100644 index 000000000..b5724b84b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:620336bdd922435ee42b83cb25691d9e762e889c9d051dc9248dea6a995d73bc +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Normal.png new file mode 100644 index 000000000..49fcf0c57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b047a5333d017d4f872fc01474d6dd4e5be1ad4516a688bc519501cdfdf26cc5 +size 176 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Overlay.png new file mode 100644 index 000000000..76673a63e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2afc2175477bf2822b6df58feaa87401875e61fc5ca58ece56f55411dfd1bf60 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Screen.png new file mode 100644 index 000000000..9d2e596e7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2927660c0739424f62be298ad2da0b3c06b69362c665e52f8efdb281dc6613fa +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Subtract.png new file mode 100644 index 000000000..1ab310b89 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestAtop_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f23c0af3155c58de5594a423ac22f97288ad2d802c22f8ec89a9d32e9b363982 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Add.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Darken.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-HardLight.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Lighten.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Multiply.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Normal.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Overlay.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Screen.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Subtract.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestIn_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Add.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Darken.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-HardLight.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Lighten.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Multiply.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Normal.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Overlay.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Screen.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Subtract.png new file mode 100644 index 000000000..c05007ce9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOut_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d77ecd6cab29c9f68f9b1c495fe6465fc2e0f7c83b81f9d136feae3efd8c347c +size 135 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Add.png new file mode 100644 index 000000000..f605fcda3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46a33f56439bd22e8bac8a53f76a4f6589f1838f2a606fb74b53902fae64952f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Darken.png new file mode 100644 index 000000000..49fcf0c57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b047a5333d017d4f872fc01474d6dd4e5be1ad4516a688bc519501cdfdf26cc5 +size 176 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-HardLight.png new file mode 100644 index 000000000..35b66a733 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b87e2dfb5166737c6e4d689b163937a948d993203b70b58b95334d0672ef997f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Lighten.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Multiply.png new file mode 100644 index 000000000..b5724b84b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:620336bdd922435ee42b83cb25691d9e762e889c9d051dc9248dea6a995d73bc +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Normal.png new file mode 100644 index 000000000..49fcf0c57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b047a5333d017d4f872fc01474d6dd4e5be1ad4516a688bc519501cdfdf26cc5 +size 176 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Overlay.png new file mode 100644 index 000000000..76673a63e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2afc2175477bf2822b6df58feaa87401875e61fc5ca58ece56f55411dfd1bf60 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Screen.png new file mode 100644 index 000000000..9d2e596e7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2927660c0739424f62be298ad2da0b3c06b69362c665e52f8efdb281dc6613fa +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Subtract.png new file mode 100644 index 000000000..1ab310b89 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-DestOver_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f23c0af3155c58de5594a423ac22f97288ad2d802c22f8ec89a9d32e9b363982 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Add.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Darken.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-HardLight.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Lighten.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Multiply.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Normal.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Overlay.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Screen.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Subtract.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Dest_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Add.png new file mode 100644 index 000000000..e55378a70 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b06af1e79bd0bd98b638bd2cd6cc80a25613723d8c7143abcce95e0b801b089d +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Darken.png new file mode 100644 index 000000000..7333d02c2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84ca3aad81a27c5d11fefda37153dbdbc11ff2d84e3c57711f21d5b22ecde8f +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-HardLight.png new file mode 100644 index 000000000..20e8f35a9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8d16b2057686a61d4be59dbffc052cc03c328c36fcff65ad5ad2b53529d0dbf +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Lighten.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Multiply.png new file mode 100644 index 000000000..9ab6cc778 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45283aa3e74d93b384df71feeac379a3911582fabdc0145e94a6eb488afe4d7b +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Normal.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Overlay.png new file mode 100644 index 000000000..2205dcba1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8500bb6fa35ef940302d9aa9d391077af78f8c509c93900f191e2e080b308d7f +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Screen.png new file mode 100644 index 000000000..1c55a5890 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdeac033f313e0151bd177678c697499f8f09c31df65d4e3a9b512f369c90da5 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Subtract.png new file mode 100644 index 000000000..adef2c3ae --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcAtop_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a26c4fb100ccb1026def8738856423e945cb193d8cd1c93365a08f4b3d6dad3 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Add.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Darken.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-HardLight.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Lighten.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Multiply.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Normal.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Overlay.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Screen.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Subtract.png new file mode 100644 index 000000000..f59ea17c0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcIn_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c441d50b1a5f7403cdf86563a38a9e380ae2cb2bb335880d341556f6ce1f3f63 +size 147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Add.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Darken.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-HardLight.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Lighten.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Multiply.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Normal.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Overlay.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Screen.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Subtract.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOut_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Add.png new file mode 100644 index 000000000..f605fcda3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46a33f56439bd22e8bac8a53f76a4f6589f1838f2a606fb74b53902fae64952f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Darken.png new file mode 100644 index 000000000..49fcf0c57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b047a5333d017d4f872fc01474d6dd4e5be1ad4516a688bc519501cdfdf26cc5 +size 176 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-HardLight.png new file mode 100644 index 000000000..76673a63e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2afc2175477bf2822b6df58feaa87401875e61fc5ca58ece56f55411dfd1bf60 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Lighten.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Multiply.png new file mode 100644 index 000000000..b5724b84b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:620336bdd922435ee42b83cb25691d9e762e889c9d051dc9248dea6a995d73bc +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Normal.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Overlay.png new file mode 100644 index 000000000..35b66a733 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b87e2dfb5166737c6e4d689b163937a948d993203b70b58b95334d0672ef997f +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Screen.png new file mode 100644 index 000000000..9d2e596e7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2927660c0739424f62be298ad2da0b3c06b69362c665e52f8efdb281dc6613fa +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Subtract.png new file mode 100644 index 000000000..2d42e2a0f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-SrcOver_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d378094f7ac82529a962ab155ec020c10c51394728f340bf20af17bf6e9bf07 +size 183 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Add.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Darken.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-HardLight.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Lighten.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Multiply.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Normal.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Overlay.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Screen.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Subtract.png new file mode 100644 index 000000000..d61a0f208 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Src_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d166c86a768bff0d93b077342b4e018e5477a9985d3cb26305b1e8c56e5d96b +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Add.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Add.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Darken.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Darken.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-HardLight.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-HardLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Lighten.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Lighten.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Multiply.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Multiply.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Normal.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Overlay.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Overlay.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Screen.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Subtract.png new file mode 100644 index 000000000..3f7a7ae6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/BlendingsDarkBlueRectBlendHotPinkRect_composition-Xor_blending-Subtract.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f01507f25ec90159d46faa286485950b3cd28aba1d20628390fef3d41539b9 +size 180 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawRichTextAlongPathHorizontal_Solid100x100_(0,0,0,255)_RichText-Path-(spiral).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawRichTextAlongPathHorizontal_Solid100x100_(0,0,0,255)_RichText-Path-(spiral).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawRichTextAlongPathHorizontal_Solid100x100_(0,0,0,255)_RichText-Path-(spiral).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawRichTextAlongPathHorizontal_Solid100x100_(0,0,0,255)_RichText-Path-(spiral).png diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawRichTextAlongPathHorizontal_Solid120x120_(0,0,0,255)_RichText-Path-(triangle).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawRichTextAlongPathHorizontal_Solid120x120_(0,0,0,255)_RichText-Path-(triangle).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawRichTextAlongPathHorizontal_Solid120x120_(0,0,0,255)_RichText-Path-(triangle).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawRichTextAlongPathHorizontal_Solid120x120_(0,0,0,255)_RichText-Path-(triangle).png diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawRichTextAlongPathHorizontal_Solid350x350_(0,0,0,255)_RichText-Path-(circle).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawRichTextAlongPathHorizontal_Solid350x350_(0,0,0,255)_RichText-Path-(circle).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawRichTextAlongPathHorizontal_Solid350x350_(0,0,0,255)_RichText-Path-(circle).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawRichTextAlongPathHorizontal_Solid350x350_(0,0,0,255)_RichText-Path-(circle).png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank100x100_type-spiral.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank100x100_type-spiral.png new file mode 100644 index 000000000..c3e55d441 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank100x100_type-spiral.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e4c861a6b42517bc868f41dbd307af168e8a6cc4145e501b4c64556c0a26430 +size 5334 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank120x120_type-triangle.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank120x120_type-triangle.png new file mode 100644 index 000000000..59f0e63e3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank120x120_type-triangle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac4bdbbbc73fa14cc311ae2af518b23faf0c7a1226dda9f60d237ecdd7ab2ae +size 4384 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank350x350_type-circle.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank350x350_type-circle.png new file mode 100644 index 000000000..8134fe060 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank350x350_type-circle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6395ec412e0b912d693d84458785b7749baf7e2a35dde1b9263af69dd602276c +size 9528 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathVertical_Rgba32_Blank250x250_type-triangle.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathVertical_Rgba32_Blank250x250_type-triangle.png new file mode 100644 index 000000000..d9a22ed35 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathVertical_Rgba32_Blank250x250_type-triangle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c861fe15240f0d19e614cfae29abe239a9b51b14c0789b681a01298bba43f4b6 +size 5180 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathVertical_Rgba32_Blank350x350_type-circle.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathVertical_Rgba32_Blank350x350_type-circle.png new file mode 100644 index 000000000..e14fabeaf --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextAlongPathVertical_Rgba32_Blank350x350_type-circle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eaa00cbf0f654adaae06c81fe72ac18bfa0dd165dec92e1e7435ac7d04aff692 +size 7443 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVertical2_Rgba32_Blank48x935.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVertical2_Rgba32_Blank48x935.png new file mode 100644 index 000000000..8801715ee --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVertical2_Rgba32_Blank48x935.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f54f5715132c2f81447205912f6c199c1dee9a2356fc402d5ee14e22d6f2a392 +size 4988 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVerticalMixed2_Rgba32_Blank48x839.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVerticalMixed2_Rgba32_Blank48x839.png new file mode 100644 index 000000000..8988f8248 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVerticalMixed2_Rgba32_Blank48x839.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6b0d58b734e6bb0ee199cdcfe00332ffead6a0cc5fd2eebb9a835a11a41a937 +size 4903 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVerticalMixed_Rgba32_Blank500x400.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVerticalMixed_Rgba32_Blank500x400.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVerticalMixed_Rgba32_Blank500x400.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVerticalMixed_Rgba32_Blank500x400.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVertical_Rgba32_Blank500x400.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVertical_Rgba32_Blank500x400.png new file mode 100644 index 000000000..350519917 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanDrawTextVertical_Rgba32_Blank500x400.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bae265a8c3aaeaeb034bb549f3bca384c5dbfd90725ca2f2ad3151d531bc5dfc +size 13195 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanFillTextVerticalMixed_Rgba32_Blank500x400.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanFillTextVerticalMixed_Rgba32_Blank500x400.png new file mode 100644 index 000000000..eadea1090 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanFillTextVerticalMixed_Rgba32_Blank500x400.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a49fb2b4eed39b98932e66c105061981d56bc8d4edcfe0397be99d1538a5acc2 +size 11079 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanFillTextVertical_Rgba32_Blank500x400.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanFillTextVertical_Rgba32_Blank500x400.png new file mode 100644 index 000000000..0ff4d859b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanFillTextVertical_Rgba32_Blank500x400.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fada233e5e7d359c3ba84e6c1debd375ddbc46e4b1e5111c0456bd0ee28526cd +size 4482 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRenderTextOutOfBoundsIssue301.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRenderTextOutOfBoundsIssue301.png new file mode 100644 index 000000000..c2d5e2a13 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRenderTextOutOfBoundsIssue301.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb7e8e4ae30d344156c60959885911420001917fe8ab3c1ca8e5439a1b755b4e +size 1131 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-Quic).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-Quic).png new file mode 100644 index 000000000..aedd4872d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-Quic).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02581fcceb6031debb560705641cb9ee3d0ea9c1cff72f5fe0260092637b59c8 +size 1957 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-Quic).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-Quic).png new file mode 100644 index 000000000..820d13df3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-Quic).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71cdeeab2372d7f1ed09f98fe2103045637111f2b807a41905bf1f197fc85e86 +size 1716 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-STR(1)-Quic).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-STR(1)-Quic).png new file mode 100644 index 000000000..4f4e3224b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-STR(1)-Quic).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df40aa73e47380e3c4e645f17b674c25432c5c0657f29735a8401e15e3411a40 +size 2604 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-STR(2)-Quic).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-STR(2)-Quic).png new file mode 100644 index 000000000..86a02b332 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-STR(2)-Quic).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:519661ae4e1e4077e3515de5fdb5ae605369dafe780c8cfb2acfe2f1ebff77d7 +size 2482 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_AlwaysOverridesPreviousColor_Blue.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_AlwaysOverridesPreviousColor_Blue.png new file mode 100644 index 000000000..5254df120 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_AlwaysOverridesPreviousColor_Blue.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bbd2c59e95ae401a038f69d9de433b56ea89c493bf5d73af54197fadf032393 +size 96 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_AlwaysOverridesPreviousColor_Khaki.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_AlwaysOverridesPreviousColor_Khaki.png new file mode 100644 index 000000000..4bd589703 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_AlwaysOverridesPreviousColor_Khaki.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27281a193533244c31acb485c7ecacc047b505042a1bbfd3c214016db2b6f7b1 +size 96 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_Argb32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_Argb32.png new file mode 100644 index 000000000..662dd0037 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_Argb32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25adfbb17267acb56770cf0de92eab85bdb4c6a3bc790b24022b618e64e70f0f +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_Rgba32.png new file mode 100644 index 000000000..662dd0037 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25adfbb17267acb56770cf0de92eab85bdb4c6a3bc790b24022b618e64e70f0f +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_RgbaVector.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_RgbaVector.png new file mode 100644 index 000000000..662dd0037 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSinglePixelType_RgbaVector.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25adfbb17267acb56770cf0de92eab85bdb4c6a3bc790b24022b618e64e70f0f +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank16x7.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank16x7.png new file mode 100644 index 000000000..113c9e069 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank16x7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:577cff471034b801e84c2df271946e59e441d0890910b949dcd7b81b25f38d58 +size 82 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank1x1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank1x1.png new file mode 100644 index 000000000..d406a3275 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank1x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8df185b0b10595bba92b871646a6b349b308221e63c2ead096e718676716bddd +size 72 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank33x32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank33x32.png new file mode 100644 index 000000000..4c6092dfc --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank33x32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1e7483e76c3b65b94b68499f93f07b2c73435353adf46638c2d1fe16d62f6a0 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank400x500.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank400x500.png new file mode 100644 index 000000000..af764cb13 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank400x500.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed2eeed8c081a355f23062a56ea80f1891745c63f8468d65771e49418b508cd6 +size 119 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank7x4.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank7x4.png new file mode 100644 index 000000000..0a126de11 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_DoesNotDependOnSize_Blank7x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c24b47a3afd5d7185d9722cd8f0bd4274489bedd4e09c8d23a66e49af405dc2 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png new file mode 100644 index 000000000..cf2790c36 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b99d68b7a4004b690bf3e2c03d408c40491926435fd1afdccd93ad23c919b20 +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png new file mode 100644 index 000000000..7631eab46 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4db5130b5c73181a950b9f3f4697a09d9486dba90fa140ace97c368c1e8550f +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_WorksOnWrappedMemoryImage_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_WorksOnWrappedMemoryImage_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png new file mode 100644 index 000000000..cf2790c36 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_WorksOnWrappedMemoryImage_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b99d68b7a4004b690bf3e2c03d408c40491926435fd1afdccd93ad23c919b20 +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_WorksOnWrappedMemoryImage_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_WorksOnWrappedMemoryImage_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png new file mode 100644 index 000000000..7631eab46 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_Region_WorksOnWrappedMemoryImage_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4db5130b5c73181a950b9f3f4697a09d9486dba90fa140ace97c368c1e8550f +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_WhenColorIsOpaque_OverridePreviousColor_Blue.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_WhenColorIsOpaque_OverridePreviousColor_Blue.png new file mode 100644 index 000000000..8570310b7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_WhenColorIsOpaque_OverridePreviousColor_Blue.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c70c09a82dfbb4db1955e417c1f24ea90178f5234bba420e71f74a094218c7c +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_WhenColorIsOpaque_OverridePreviousColor_Khaki.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_WhenColorIsOpaque_OverridePreviousColor_Khaki.png new file mode 100644 index 000000000..3730d9c35 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/Clear_WhenColorIsOpaque_OverridePreviousColor_Khaki.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dfc380297d61413eec1b9a3f634ae0489c22fb310b004e1ecf6ea26ecc28b5f +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipConstrainsOperationToClipBounds.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipConstrainsOperationToClipBounds.png new file mode 100644 index 000000000..56687b97a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipConstrainsOperationToClipBounds.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45c9a5afb6180d0ba667ace64841566fce63c27e8a537c8fffd2286682f08687 +size 28546 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x-20_y-100.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x-20_y-100.png new file mode 100644 index 000000000..54b5a6bfd --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x-20_y-100.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13ecb101e50d82f149f2e3360277696bf3ae1c915fe26b9e52132df7630dd3d1 +size 3677 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x-20_y-20.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x-20_y-20.png new file mode 100644 index 000000000..96086b63c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x-20_y-20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:698a8a68e934d84a5689d0eb2e58ef32c389c3c19149bcc668ea1cc061eb39df +size 4983 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x0_y0.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x0_y0.png new file mode 100644 index 000000000..eade21ef1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x0_y0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21367b243982ee878a6bcd59c4dd68f7c2b427c2ee9656b61b01b52f65ae462b +size 5356 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x20_y20.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x20_y20.png new file mode 100644 index 000000000..213fe08e5 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x20_y20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11a8d5aea1e3dc7bd44556970d08c63182387701546992460acf563101b1355d +size 5386 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x40_y60.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x40_y60.png new file mode 100644 index 000000000..2c0bce376 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/ClipOffset_offset_x40_y60.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdde871848dc218f91a4535e1af8333571fa88e82aaec15112e0841277e6ac2d +size 2364 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_HotPink_A150_T5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_HotPink_A150_T5.png new file mode 100644 index 000000000..1850fa1be --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_HotPink_A150_T5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e30c532eae115e1b919b53087695214e42b45bb35179af6e42a3132af05dff4 +size 4325 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_HotPink_A255_T5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_HotPink_A255_T5.png new file mode 100644 index 000000000..8a7c46329 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_HotPink_A255_T5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c17958516f87e456dd79df1f546171ba6f35b32d6b99e5fcaee1f3937037886 +size 4585 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_Red_A255_T3.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_Red_A255_T3.png new file mode 100644 index 000000000..2a59cc9ce --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_Red_A255_T3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65b8cd9c25a68e6ab6fc836cc1e4a6e3ffed3e266bad5f7de0685ce163930933 +size 4585 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_White_A255_T1.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_White_A255_T1.5.png new file mode 100644 index 000000000..e0d763c56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_White_A255_T1.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f550fc772fbd1a4295a51fdbabb26c1148c5d28fb4eb863a321b35aa6a75ccc +size 4581 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_White_A255_T15.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_White_A255_T15.png new file mode 100644 index 000000000..e0d763c56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawBeziers_White_A255_T15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f550fc772fbd1a4295a51fdbabb26c1148c5d28fb4eb863a321b35aa6a75ccc +size 4581 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon.png new file mode 100644 index 000000000..9fba12d05 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f431c391d1283c0ade7cd188d99852d30d90accf23f33febb28f1058318d2b3c +size 3657 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Dashed.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Dashed.png new file mode 100644 index 000000000..6b8971645 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Dashed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a6833ea382684b0cc99c56119cfb4ebf450fd952551d33fcd62c134072ab92f +size 7809 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Overlap.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Overlap.png new file mode 100644 index 000000000..ab6f9a348 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Overlap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81c7550b1737ecf92a84363ff4f830562be73e8ab43bd6b3656160793f1448d8 +size 5799 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Transparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Transparent.png new file mode 100644 index 000000000..bcc40e163 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawComplexPolygon__Transparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4e81d12ddbab8962e1a5068da896cf4ca208336b4bfbf0bc468a94dbf0d41c4 +size 3624 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(1).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(1).png new file mode 100644 index 000000000..6cd79f23b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(1).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2a405e64d39be85b3475f615f06eeef94c6f3f2ed577765a6fb557d3d1b4a07 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(1)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(1)_NoAntialias.png new file mode 100644 index 000000000..6cd79f23b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(1)_NoAntialias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2a405e64d39be85b3475f615f06eeef94c6f3f2ed577765a6fb557d3d1b4a07 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(5).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(5).png new file mode 100644 index 000000000..6cd79f23b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(5).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2a405e64d39be85b3475f615f06eeef94c6f3f2ed577765a6fb557d3d1b4a07 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(5)_NoAntialias.png new file mode 100644 index 000000000..6cd79f23b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLinesInvalidPoints_Rgba32_T(5)_NoAntialias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2a405e64d39be85b3475f615f06eeef94c6f3f2ed577765a6fb557d3d1b4a07 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_DashDotDot_Rgba32_Black_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_DashDotDot_Rgba32_Black_A(1)_T(5)_NoAntialias.png new file mode 100644 index 000000000..53f315caf --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_DashDotDot_Rgba32_Black_A(1)_T(5)_NoAntialias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0797ec93efa1dc5767ecd4531f63b79817f031b014ead37e26675594fee88923 +size 997 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_DashDot_Rgba32_Yellow_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_DashDot_Rgba32_Yellow_A(1)_T(5)_NoAntialias.png new file mode 100644 index 000000000..9131466bc --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_DashDot_Rgba32_Yellow_A(1)_T(5)_NoAntialias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:955772b67450ea0d9ef13f2b8754750939d3850f34d3b589ee653d2d5ca5af53 +size 1011 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Dash_Rgba32_White_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Dash_Rgba32_White_A(1)_T(5)_NoAntialias.png new file mode 100644 index 000000000..ade213629 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Dash_Rgba32_White_A(1)_T(5)_NoAntialias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c647187ebcf6ef794f463454c04855bb0e064a7ddb37dd62b0a1b0563305f394 +size 1072 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Dot_Rgba32_LightGreen_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Dot_Rgba32_LightGreen_A(1)_T(5)_NoAntialias.png new file mode 100644 index 000000000..130bc7b49 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Dot_Rgba32_LightGreen_A(1)_T(5)_NoAntialias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d34de570982d91b9f1b6f836957e8bdd454c9867948084487bc260f54d50a0fc +size 905 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapButt_Rgba32_Yellow_A(1)_T(5).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapButt_Rgba32_Yellow_A(1)_T(5).png new file mode 100644 index 000000000..a27612003 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapButt_Rgba32_Yellow_A(1)_T(5).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a97f219e885cc7cc257cf2a06d1b75ba23b77c3cb286e2bdd2875ceee3f6c8af +size 2805 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapRound_Rgba32_Yellow_A(1)_T(5).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapRound_Rgba32_Yellow_A(1)_T(5).png new file mode 100644 index 000000000..57c6b0fab --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapRound_Rgba32_Yellow_A(1)_T(5).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:456c72ce3d8da61161d7de3fb7d809e0c51a71c0e1b8a1ac68bffc25683eee4b +size 4200 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapSquare_Rgba32_Yellow_A(1)_T(5).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapSquare_Rgba32_Yellow_A(1)_T(5).png new file mode 100644 index 000000000..7d2189407 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_EndCapSquare_Rgba32_Yellow_A(1)_T(5).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:958f8999354475e6361b124575d8206d4bc17e3ae067bf4966bd7904219eeb7a +size 3037 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleMiter_Rgba32_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleMiter_Rgba32_Yellow_A(1)_T(10).png new file mode 100644 index 000000000..835c242ce --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleMiter_Rgba32_Yellow_A(1)_T(10).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cec5888ca8a55f6e5d561c27d7276fc463b68f6a7f727ddc15b98c7da515f831 +size 2192 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleRound_Rgba32_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleRound_Rgba32_Yellow_A(1)_T(10).png new file mode 100644 index 000000000..1c9a755bb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleRound_Rgba32_Yellow_A(1)_T(10).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18380f5cf553eb6104a995336f8db55b1aaefb1fa7bbe7b971987f8d7c256e1d +size 2190 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleSquare_Rgba32_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleSquare_Rgba32_Yellow_A(1)_T(10).png new file mode 100644 index 000000000..95e830cd4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_JointStyleSquare_Rgba32_Yellow_A(1)_T(10).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e176cf6bdb1df68a54e1633f2ac38f0034f0b76c4d8b3b2022be76fba6786ad +size 2187 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Bgr24_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Bgr24_Yellow_A(1)_T(10).png new file mode 100644 index 000000000..5c59b9b1f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Bgr24_Yellow_A(1)_T(10).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce1087527c29764c71a0afd1a7c99df50d48e977cda6c0739141978607d9d622 +size 2129 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Rgba32_White_A(0.6)_T(10).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Rgba32_White_A(0.6)_T(10).png new file mode 100644 index 000000000..249ec8438 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Rgba32_White_A(0.6)_T(10).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b040917d9bc03bce714c5fbea276566e3c31ac76372f586be25c56f8de832ba +size 2175 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Rgba32_White_A(1)_T(2.5).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Rgba32_White_A(1)_T(2.5).png new file mode 100644 index 000000000..78d5f08bb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Rgba32_White_A(1)_T(2.5).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d42c76e47416d5fcf8669ffce6275a81139253ee0e7b577ce393cdb16a65152c +size 2012 diff --git a/tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Rgba32_White_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Rgba32_White_A(1)_T(5)_NoAntialias.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/DrawLinesTests/DrawLines_Simple_Rgba32_White_A(1)_T(5)_NoAntialias.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawLines_Simple_Rgba32_White_A(1)_T(5)_NoAntialias.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingAddArc_359.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingAddArc_359.png new file mode 100644 index 000000000..b74588b7b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingAddArc_359.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:470b04d20045a42386615f0e9ae91ddf55b6276e51458cc0360c180d89c790b2 +size 1787 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingAddArc_360.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingAddArc_360.png new file mode 100644 index 000000000..17ac889de --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingAddArc_360.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:001414679819cfdf8b0f59a89646ed6bdb042e5008f899f1dad3819d213a19f9 +size 1718 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingArcTo_False.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingArcTo_False.png new file mode 100644 index 000000000..cc731a31a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingArcTo_False.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55dba01c08ac1096d3153dc4234932b02894442f1efe72a036b7ae56f53d21ad +size 1558 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingArcTo_True.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingArcTo_True.png new file mode 100644 index 000000000..cc731a31a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathCircleUsingArcTo_True.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55dba01c08ac1096d3153dc4234932b02894442f1efe72a036b7ae56f53d21ad +size 1558 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathClippedOnTop.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathClippedOnTop.png new file mode 100644 index 000000000..47a0330ff --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathClippedOnTop.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecef2543ded88cec8ddbc9a34b1c01d45c315e6a21ec6501ce70797d97288c6d +size 240 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathExtendingOffEdgeOfImageShouldNotBeCropped.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathExtendingOffEdgeOfImageShouldNotBeCropped.png new file mode 100644 index 000000000..c47d700e5 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPathExtendingOffEdgeOfImageShouldNotBeCropped.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5027c15dab6e0a3e2cb88f6dba73753b19fd2f74f6eff6aa530fdad7cb79ec77 +size 5787 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_HotPink_A150_T5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_HotPink_A150_T5.png new file mode 100644 index 000000000..b9fbf99f6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_HotPink_A150_T5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d84cf12e6831d73cb271ed5fbf2b22e52603ea6d95aaa1736fd86829dbcb7f94 +size 7739 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_HotPink_A255_T5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_HotPink_A255_T5.png new file mode 100644 index 000000000..488e37a9b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_HotPink_A255_T5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6225d9d921256a34c1d51b4a674953644380f7840d7ff5d514365e0e336d265 +size 15137 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_Red_A255_T3.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_Red_A255_T3.png new file mode 100644 index 000000000..6e6a1b77e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_Red_A255_T3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b92fc8f096ef187d1e661c36dfefe1188d141f949f18948dd50c76fd8e273e4b +size 14367 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_White_A255_T1.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_White_A255_T1.5.png new file mode 100644 index 000000000..9ea280b2a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_White_A255_T1.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58bb5e0daa0f1dc3ae303189688d7b6044110916847d3eded5e31514d16fccf4 +size 7512 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_White_A255_T15.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_White_A255_T15.png new file mode 100644 index 000000000..fe3282db4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPath_White_A255_T15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9ef81afbf079de5da44f0973a2a0515aa4c1899d7c2b892e91bb333a153a68b +size 7967 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygonRectangular_Transformed_Rgba32_BasicTestPattern100x100.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygonRectangular_Transformed_Rgba32_BasicTestPattern100x100.png new file mode 100644 index 000000000..74f687702 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygonRectangular_Transformed_Rgba32_BasicTestPattern100x100.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03956bafa8a1d949c06df75c8b6f824f21b33335de7306c2ff06632fdb8b47b1 +size 588 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Bgr24_Yellow_A(1)_T(10).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Bgr24_Yellow_A(1)_T(10).png new file mode 100644 index 000000000..592cee017 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Bgr24_Yellow_A(1)_T(10).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d883ca62ee26a0ffd7a68ed8aa171fe2a12b56337d8f7368f7c232b48531d8b1 +size 3114 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(0.6)_T(10).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(0.6)_T(10).png new file mode 100644 index 000000000..e8157776e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(0.6)_T(10).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:943e09dd6e4e80f8b76af7dc558e47e38c9abb21a40355d42f7fb82dc7744826 +size 3084 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(1)_T(2.5).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(1)_T(2.5).png new file mode 100644 index 000000000..16d14ed74 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(1)_T(2.5).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c769b4e5e161d3a121f6ab01c4733452149568758b18fef2abe56cc351b1828 +size 3072 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(1)_T(5)_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(1)_T(5)_NoAntialias.png new file mode 100644 index 000000000..e9e3ae1e3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Rgba32_White_A(1)_T(5)_NoAntialias.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54ea5ce83e31694e5d4813ae10d431bf73bd0c2c1bb3acbb6490dad2b15b74ff +size 1264 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Transformed_Rgba32_BasicTestPattern250x350.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Transformed_Rgba32_BasicTestPattern250x350.png new file mode 100644 index 000000000..e021f6f0e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawPolygon_Transformed_Rgba32_BasicTestPattern250x350.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a270570cfcc2c7a9da7d678de596c08cc3158d062f7a068debaf175c97f27153 +size 8834 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextArabic_Solid500x200_(0,0,0,255)_RichText-Arabic-F(32).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextArabic_Solid500x200_(0,0,0,255)_RichText-Arabic-F(32).png new file mode 100644 index 000000000..9d1090e30 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextArabic_Solid500x200_(0,0,0,255)_RichText-Arabic-F(32).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f63ac0247fc56eb2812847f88a0bbc0ac12634374bfcdad017ee53771e310f0b +size 3122 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextArabic_Solid500x300_(0,0,0,255)_RichText-Arabic-F(40).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextArabic_Solid500x300_(0,0,0,255)_RichText-Arabic-F(40).png new file mode 100644 index 000000000..0e9aa3923 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextArabic_Solid500x300_(0,0,0,255)_RichText-Arabic-F(40).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d7edc251c9e67df4dfdab4524f22ddb491f67cd085f8790c5c450ae39bfec04 +size 3916 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextRainbow_Solid500x200_(0,0,0,255)_RichText-Rainbow-F(32).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextRainbow_Solid500x200_(0,0,0,255)_RichText-Rainbow-F(32).png new file mode 100644 index 000000000..81fe22adb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextRainbow_Solid500x200_(0,0,0,255)_RichText-Rainbow-F(32).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc8d2eb8845cc1a7de9ed11a7cc4d8e750b372817255351b3d3c1c8fc7cd29ef +size 8671 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextRainbow_Solid500x300_(0,0,0,255)_RichText-Rainbow-F(40).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextRainbow_Solid500x300_(0,0,0,255)_RichText-Rainbow-F(40).png new file mode 100644 index 000000000..7e8d52c8a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichTextRainbow_Solid500x300_(0,0,0,255)_RichText-Rainbow-F(40).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2f245d2324ff1e5bff2271ae233d395667f122b306afc5d70b429ed3e456763 +size 11434 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichText_Solid500x200_(0,0,0,255)_RichText-F(32).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichText_Solid500x200_(0,0,0,255)_RichText-F(32).png new file mode 100644 index 000000000..90dd9b2a0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichText_Solid500x200_(0,0,0,255)_RichText-F(32).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2e18fc2414f134a6409a582a2bba428bdc6c3a3d07f209759a997aa4ca9342f +size 9020 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichText_Solid500x300_(0,0,0,255)_RichText-F(40).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichText_Solid500x300_(0,0,0,255)_RichText-F(40).png new file mode 100644 index 000000000..39bded808 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/DrawRichText_Solid500x300_(0,0,0,255)_RichText-F(40).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e2a99a9ba0f44742af59b7d55dcb1758541410fd0df83120dad553c0577eb73 +size 11792 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-False.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-False.png new file mode 100644 index 000000000..0d70e48eb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-False.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:559421345d453431dad84bc84c1afd920dafadaa09cc421d009ee039feed99a9 +size 10038 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-True.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-True.png new file mode 100644 index 000000000..e15e94223 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-True.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9ff15e1e9b776fe515cb7f7bbc4cb54a7556c5472788a860b99618d4d8a3578 +size 18516 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FallbackFontRendering_Rgba32_Solid400x200_(255,255,255,255).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FallbackFontRendering_Rgba32_Solid400x200_(255,255,255,255).png new file mode 100644 index 000000000..23ce361d7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FallbackFontRendering_Rgba32_Solid400x200_(255,255,255,255).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7d3de4ebb39ba8740a8329d7044056d5ff8ac924b6ee78032af36141ac6a4b3 +size 1794 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill.png new file mode 100644 index 000000000..474e3f1cd --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d498f83cc3cf2a2f47646f7909479e6cbba8ca2dba07e01e99d0023f02762e51 +size 2279 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill__Overlap.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill__Overlap.png new file mode 100644 index 000000000..1e380775b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill__Overlap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9030fe85b5d13168b4308b9bab1af7ddf6cab90a601e3d90ef5b0d8094379fda +size 2600 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill__Transparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill__Transparent.png new file mode 100644 index 000000000..ea9167458 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillComplexPolygon_SolidFill__Transparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bf8e56e1e7357fd33c076d1b75788706f8cd8629c3a557471770a22b4aba95e +size 2266 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.10.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.10.png new file mode 100644 index 000000000..43ad24b93 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38a7090de2e49df30cbfa51953f77d5e0584438602265ff6a48ad75c53e4e8e6 +size 674 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.40.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.40.png new file mode 100644 index 000000000..c0decb5c3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.40.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0890bc4fde8013d814458a2ec34ed1e51c232e1e99c31b84dc7ae023c8bf62a1 +size 1548 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.80.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.80.png new file mode 100644 index 000000000..5b210ff3a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_0.80.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6412666ed00fd3b2dcf6ce7645dec3fdfa5fd029651f8166b9e7f34bf5b47d4 +size 1704 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.00.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.00.png new file mode 100644 index 000000000..e6f419818 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47225f2e7993b60477676d92f14404a0073a6da793e03fdc9c6de044a428e8f6 +size 1907 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.20.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.20.png new file mode 100644 index 000000000..75186d636 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba202c83d3bf4a900af8654ea8b231393a4182d7befbbf895fff418e37bcadf1 +size 1996 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.60.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.60.png new file mode 100644 index 000000000..0db8ffed5 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_1.60.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:804e9a7d31d80705e5005a39edbe28dd8ae766b2885e38e45933a62de2b9509e +size 2114 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_2.00.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_2.00.png new file mode 100644 index 000000000..4183ebed9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushAxisParallelEllipsesWithDifferentRatio_2.00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffc51d54e067ac514425d9ea9b19f2fc5f0b4af11451be153c1da29885ede640 +size 2225 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_00deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_00deg.png new file mode 100644 index 000000000..43ad24b93 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_00deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38a7090de2e49df30cbfa51953f77d5e0584438602265ff6a48ad75c53e4e8e6 +size 674 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_30deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_30deg.png new file mode 100644 index 000000000..06c04c71c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_30deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e2dc06fdec0dd48bd1e4e29420bf25e094fcf98263634e8efb0b11f85f5892b +size 932 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_45deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_45deg.png new file mode 100644 index 000000000..90d85f0fb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_45deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a742a4e1ff6786d4435a3abfae5f3de97b8409434a7fc7c7d8c567cdf523c7b8 +size 796 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_90deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_90deg.png new file mode 100644 index 000000000..27a056b60 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.10_AT_90deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dbe6cef98d0bd165a63aeb282e21ac1ff480171af32d9d2babfe75113a5da57 +size 535 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_00deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_00deg.png new file mode 100644 index 000000000..c0decb5c3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_00deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0890bc4fde8013d814458a2ec34ed1e51c232e1e99c31b84dc7ae023c8bf62a1 +size 1548 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_30deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_30deg.png new file mode 100644 index 000000000..82fe59ac1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_30deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97cf0529ee30218df153d180864938918f9ef118cdae35f6d0d347e088b41776 +size 1445 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_45deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_45deg.png new file mode 100644 index 000000000..a3f902e34 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_45deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6aa7cda377b908fb4c4cd9ae1b8e2dfc493d82f6771a1ad3e074657c8c431c0f +size 1329 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_90deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_90deg.png new file mode 100644 index 000000000..d8aee09ab --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.40_AT_90deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38c1d989c31baee7445c099b643aec6a7accef8bff7455bd5f44c86133fbf580 +size 1451 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_00deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_00deg.png new file mode 100644 index 000000000..5b210ff3a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_00deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6412666ed00fd3b2dcf6ce7645dec3fdfa5fd029651f8166b9e7f34bf5b47d4 +size 1704 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_30deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_30deg.png new file mode 100644 index 000000000..d96dd5716 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_30deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd10694b16b98783c3aa16ac9e50501fff4c5c9a5dd9a78ee91dd2af8c949f8c +size 1687 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_45deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_45deg.png new file mode 100644 index 000000000..322d69db7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_45deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:405686e70c0f341f219496dd0b34e7ad5373ac43eb38358dee9a5e59d67cc77a +size 1674 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_90deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_90deg.png new file mode 100644 index 000000000..f24da8215 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_0.80_AT_90deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d75fa6b243b87202ec4d7a599378c9b4195c15c772a36c7aeafb64e41263ce3 +size 1597 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_00deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_00deg.png new file mode 100644 index 000000000..e6f419818 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_00deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47225f2e7993b60477676d92f14404a0073a6da793e03fdc9c6de044a428e8f6 +size 1907 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_30deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_30deg.png new file mode 100644 index 000000000..3e9e0fd81 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_30deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7cdc8e25d6545f4d5ce0265dd152e58748f7740d071e257af671abca5f36bd7 +size 1801 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_45deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_45deg.png new file mode 100644 index 000000000..f1b795aab --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_45deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ae44f19a7e2c1aba894bf706a33b2fd533e1718a5c895132007db06eee5ffdc +size 1861 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_90deg.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_90deg.png new file mode 100644 index 000000000..e6f419818 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushRotatedEllipsesWithDifferentRatio_1.00_AT_90deg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47225f2e7993b60477676d92f14404a0073a6da793e03fdc9c6de044a428e8f6 +size 1907 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushWithEqualColorsReturnsUnicolorImage.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushWithEqualColorsReturnsUnicolorImage.png new file mode 100644 index 000000000..5510cbb77 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillEllipticGradientBrushWithEqualColorsReturnsUnicolorImage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb27d43cc9608027f87b0b9dfb56404a3c6a7f5de3a86746836bdf1756b01559 +size 82 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawLandscapeImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawLandscapeImage_Rgba32.png new file mode 100644 index 000000000..f6793dc92 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawLandscapeImage_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a772b2e9f117174a54de856c3a9b3ad0e592b6c45d56f0f7930cafe424367370 +size 24695 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawNegativeOffsetImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawNegativeOffsetImage_Rgba32.png new file mode 100644 index 000000000..eb08e65a4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawNegativeOffsetImage_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3856dd885f97801efd250f858c6c287b4c0ab5b05dc9f6eca5defc2412b344b +size 100634 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawOffsetImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawOffsetImage_Rgba32.png new file mode 100644 index 000000000..c3a129338 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawOffsetImage_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3aa1aac6aa2484bf7eae2e6fd08de4c7b6110833e19e7ff4b217e005192933e +size 100593 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawPortraitImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawPortraitImage_Rgba32.png new file mode 100644 index 000000000..74465715e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanDrawPortraitImage_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c5046daca9f61c66a91e323c2762fa6eb86bcc0a75430d34acacb774319af95 +size 18999 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanOffsetImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanOffsetImage_Rgba32.png new file mode 100644 index 000000000..1fe5826ba --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanOffsetImage_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5267aef9d7cf7f6adef2b84332a814aa8891ecf9060d142cec534d586b3ca55e +size 73970 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanOffsetViaBrushImage_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanOffsetViaBrushImage_Rgba32.png new file mode 100644 index 000000000..1fe5826ba --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushCanOffsetViaBrushImage_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5267aef9d7cf7f6adef2b84332a814aa8891ecf9060d142cec534d586b3ca55e +size 73970 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushUseBrushOfDifferentPixelType_Bgra32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushUseBrushOfDifferentPixelType_Bgra32.png new file mode 100644 index 000000000..109bad9cf --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushUseBrushOfDifferentPixelType_Bgra32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64f79e92ad86f3efb9cdbafe15c7fffb04694362d1d5cceaa5ea613c68a940f4 +size 28900 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushUseBrushOfDifferentPixelType_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushUseBrushOfDifferentPixelType_Rgba32.png new file mode 100644 index 000000000..109bad9cf --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillImageBrushUseBrushOfDifferentPixelType_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64f79e92ad86f3efb9cdbafe15c7fffb04694362d1d5cceaa5ea613c68a940f4 +size 28900 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;000080FF@0.2;90EE90FF@0.5;90EE90FF@0.9;].png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;000080FF@0.2;90EE90FF@0.5;90EE90FF@0.9;].png new file mode 100644 index 000000000..bcb3f3844 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;000080FF@0.2;90EE90FF@0.5;90EE90FF@0.9;].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da374c22ddaefda0d975a5c50f3efc3738e000314a73c878ad9dbf1455f47e8b +size 4433 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;90EE90FF@0.5;FF0000FF@1;].png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;90EE90FF@0.5;FF0000FF@1;].png new file mode 100644 index 000000000..1999238d8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,0)_TO_(499,499)__[000080FF@0;90EE90FF@0.5;FF0000FF@1;].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03880367b9a9a528f01b4d0a398d48ed069ad1e3f1d3cae7519e298f121b3307 +size 7037 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,499)_TO_(499,0)__[000080FF@0;90EE90FF@0.2;FFFF00FF@0.5;FF0000FF@0.9;].png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,499)_TO_(499,0)__[000080FF@0;90EE90FF@0.2;FFFF00FF@0.5;FF0000FF@0.9;].png new file mode 100644 index 000000000..2989aa05e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(0,499)_TO_(499,0)__[000080FF@0;90EE90FF@0.2;FFFF00FF@0.5;FF0000FF@0.9;].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e0ce379320f6b2b225d8d0c26e2e956aa484341b0dac43f8523e51361a56cd1 +size 6978 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(499,499)_TO_(0,0)__[000080FF@0;90EE90FF@0.7;FFFF00FF@0.8;000080FF@0.9;].png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(499,499)_TO_(0,0)__[000080FF@0;90EE90FF@0.7;FFFF00FF@0.8;000080FF@0.9;].png new file mode 100644 index 000000000..348ab784a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushArbitraryGradients_(499,499)_TO_(0,0)__[000080FF@0;90EE90FF@0.7;FFFF00FF@0.8;000080FF@0.9;].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19bd1e4651295d02b7635609a029e236a1c2c5a1db96c8191f87fcaf69c4ac0b +size 6858 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushBrushApplicatorIsThreadSafeIssue1044.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushBrushApplicatorIsThreadSafeIssue1044.png new file mode 100644 index 000000000..714585d94 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushBrushApplicatorIsThreadSafeIssue1044.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbebd336f68bb4232d683ed6ae5e8659cd274ed5ac3c72d41b9e0935c5bd3139 +size 4674 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_BottomLeft.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_BottomLeft.png new file mode 100644 index 000000000..252662fab --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_BottomLeft.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e826ac8ef1c72a6f2b050ce10a649f905269e47461a72137335fbbcdffd03ff8 +size 1972 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_BottomRight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_BottomRight.png new file mode 100644 index 000000000..c10d3a435 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_BottomRight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fb2276b9f6c566156011a12a61d6517c7bc2069068a0eb1ea6e3f69a4a081a0 +size 1719 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_TopLeft.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_TopLeft.png new file mode 100644 index 000000000..8bbe40ccd --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_TopLeft.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e06f9d71559cccde2950eef23cfe7041e1e00f66d08cc3013b1489bb8bca161 +size 1719 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_TopRight.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_TopRight.png new file mode 100644 index 000000000..4f8cc9dbd --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDiagonalReturnsCorrectImages_TopRight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b676834eb7a429b08ac4a437f00de60f3511eef118887a3c0650bde750af1983 +size 1971 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Argb32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Argb32.png new file mode 100644 index 000000000..8dda79f32 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Argb32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc7f7d5e5950caa162e7ec82b617129a2683107518e352b5768eb015b240285 +size 87 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Rgb24.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Rgb24.png new file mode 100644 index 000000000..8dda79f32 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Rgb24.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc7f7d5e5950caa162e7ec82b617129a2683107518e352b5768eb015b240285 +size 87 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Rgba32.png new file mode 100644 index 000000000..8dda79f32 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushDoesNotDependOnSinglePixelType_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc7f7d5e5950caa162e7ec82b617129a2683107518e352b5768eb015b240285 +size 87 diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/GradientsWithTransparencyOnExistingBackground_Rgba32_Blank200x200.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushGradientsWithTransparencyOnExistingBackground_Rgba32_Blank200x200.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/GradientsWithTransparencyOnExistingBackground_Rgba32_Blank200x200.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushGradientsWithTransparencyOnExistingBackground_Rgba32_Blank200x200.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_DontFill.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_DontFill.png new file mode 100644 index 000000000..81b15d01e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_DontFill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6edbc17395e460588a4fbb50b430e4b7979c5f25b2f521d8a6fbb972ca9107d5 +size 309 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_None.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_None.png new file mode 100644 index 000000000..86978ce00 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_None.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff0a58b52f199e1c96255546fae91c3eadc9055786848ec061fe637c94346350 +size 131 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_Reflect.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_Reflect.png new file mode 100644 index 000000000..42b48642c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_Reflect.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ddf6564ea8a8c8c7c8e919cb046b82ed5e7932ea8c2b4a1b23f5ee3914463ea +size 151 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_Repeat.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_Repeat.png new file mode 100644 index 000000000..72d55008e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalGradientWithRepMode_Repeat.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:652c64d699abdcf15f858be4479e4837a0310ee3749028fb46a340e736c4080f +size 145 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalReturnsUnicolorColumns.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalReturnsUnicolorColumns.png new file mode 100644 index 000000000..ba25a6969 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushHorizontalReturnsUnicolorColumns.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69c58759a0fb50eb92431e835a8ac2fc7a97032bbc88f321c56832e6b6271f49 +size 148 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushMultiplePointGradients_(0,0)_TO_(199,199)__[000000FF@0;0000FFFF@0.25;FF0000FF@0.5;FFFFFFFF@0.75;00FF00FF@1;].png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushMultiplePointGradients_(0,0)_TO_(199,199)__[000000FF@0;0000FFFF@0.25;FF0000FF@0.5;FFFFFFFF@0.75;00FF00FF@1;].png new file mode 100644 index 000000000..449b5dbed --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushMultiplePointGradients_(0,0)_TO_(199,199)__[000000FF@0;0000FFFF@0.25;FF0000FF@0.5;FFFFFFFF@0.75;00FF00FF@1;].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15351ad42f0cd94694323203d78efdc0619777f8c20be2ca11ffb7ca1e4a7e04 +size 2135 diff --git a/tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/RotatedGradient.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushRotatedGradient.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/GradientBrushes/FillLinearGradientBrushTests/RotatedGradient.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushRotatedGradient.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushVerticalBrushReturnsUnicolorRows.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushVerticalBrushReturnsUnicolorRows.png new file mode 100644 index 000000000..d62d7aecd --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushVerticalBrushReturnsUnicolorRows.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3187dbe8408a733123fe1a15f4245002858c70586d0fb1f31f9bdaa9035bee27 +size 168 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.1_0.3_0.6.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.1_0.3_0.6.png new file mode 100644 index 000000000..3d75bf3e0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.1_0.3_0.6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd27868ebc84bc9614f78540f96ace5082dd4e5f2deacf22d684a9aa58835a5a +size 116 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.2_0.4_0.6_0.8.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.2_0.4_0.6_0.8.png new file mode 100644 index 000000000..1ffbe1b0e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.2_0.4_0.6_0.8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e9edc0787ab2997d19da66c0b59d836c8e0acd9422834a01f60d35de37a2d15 +size 110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.5.png new file mode 100644 index 000000000..842796a9c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithDoubledStopsProduceDashedPatterns_0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f49d7730c8e8c52f2b072bbe2ff2717464aad4eebc14ec0bda20596e08e7f7a5 +size 109 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithEqualColorsReturnsUnicolorImage.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithEqualColorsReturnsUnicolorImage.png new file mode 100644 index 000000000..5510cbb77 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillLinearGradientBrushWithEqualColorsReturnsUnicolorImage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb27d43cc9608027f87b0b9dfb56404a3c6a7f5de3a86746836bdf1756b01559 +size 82 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-20).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-20).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-20).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-49).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-49).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-49).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-50).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-50).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-50).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-60).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-60).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_-60).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_0).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-110_0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-99_0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-99_0).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(-99_0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-20).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-20).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-20).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-20).png diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-49).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-49).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_-49).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-49).png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-50).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-50).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-50).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-60).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-60).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_-60).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_0).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillOutsideBoundsTests/DrawCircleOutsideBoundsDrawingArea_(0_0).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(0_0).png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-20).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-20).png new file mode 100644 index 000000000..915421f84 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-20).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a22b4c13787f4844a144a78a877f3bdf2909d14c978499239bd4b4bcd5ee7276 +size 359 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-49).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-49).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-49).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-50).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-50).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-50).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-60).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-60).png new file mode 100644 index 000000000..99f68c1f0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_-60).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2c4dfb8665ad8cae68673c8deae206894bbd48b8b00990d6a3104060eafbb5 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_0).png new file mode 100644 index 000000000..e7fe3a68c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(110_0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d22933f51af79811941478c040765ad110220539e6b8a80f1482396728734c5e +size 459 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(99_0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(99_0).png new file mode 100644 index 000000000..7b18e7521 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillOutsideBoundsDrawCircleOutsideBoundsDrawingArea_(99_0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b96f610313379fdff50ad3b6a3f8f043cfdad79ea10b36d195ae43f984e5cda6 +size 492 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPathTests/FillPathArcToAlternates.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathArcToAlternates.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPathTests/FillPathArcToAlternates.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathArcToAlternates.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathCanvasArcs.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathCanvasArcs.png new file mode 100644 index 000000000..a5ee74244 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathCanvasArcs.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b93a5479c1efd00cf61b0e4a27e1e484cb98235033b365f3334321f82c53b668 +size 1459 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillComplex.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillComplex.png new file mode 100644 index 000000000..30cec9964 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillComplex.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14841d28a0bc5218d7b5d969d990a6df51757b77b0065f19bae2263aec70e1c0 +size 2783 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillRectangleWithDifferentColors.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillRectangleWithDifferentColors.png new file mode 100644 index 000000000..9026bab6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillRectangleWithDifferentColors.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33d6a4d77f6d9418dac470876aa6aa2c5bd274e6107b93579daf14f90cbfa854 +size 136 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillRectangleWithDifferentColors_Rgba32_Blank10x10.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillRectangleWithDifferentColors_Rgba32_Blank10x10.png new file mode 100644 index 000000000..9026bab6e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillRectangleWithDifferentColors_Rgba32_Blank10x10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33d6a4d77f6d9418dac470876aa6aa2c5bd274e6107b93579daf14f90cbfa854 +size 136 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColors.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColors.png new file mode 100644 index 000000000..ae3660f5d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColors.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9808622aa1a16df85ec3911f95072815a4f1cada6fdb10ed89ad79d732edecfb +size 332 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColorsCenter.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColorsCenter.png new file mode 100644 index 000000000..ffe949b9e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColorsCenter.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19d71f16b40889bbd18033a1f591d6530a5c4aa0f0ffcd7b2da882c720d35b9b +size 368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColorsCenter_Rgba32_Blank20x20.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColorsCenter_Rgba32_Blank20x20.png new file mode 100644 index 000000000..ffe949b9e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColorsCenter_Rgba32_Blank20x20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19d71f16b40889bbd18033a1f591d6530a5c4aa0f0ffcd7b2da882c720d35b9b +size 368 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColors_Rgba32_Blank20x20.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColors_Rgba32_Blank20x20.png new file mode 100644 index 000000000..ae3660f5d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithDifferentColors_Rgba32_Blank20x20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9808622aa1a16df85ec3911f95072815a4f1cada6fdb10ed89ad79d732edecfb +size 332 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithGreyscale.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithGreyscale.png new file mode 100644 index 000000000..8d4a81f75 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithGreyscale.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f38baa9ef4e4fa2484a2036f2b03f0aed4f2d82b5ffd42cba01643a12552621c +size 240 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithGreyscale_HalfSingle_Blank20x20.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithGreyscale_HalfSingle_Blank20x20.png new file mode 100644 index 000000000..8d4a81f75 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillTriangleWithGreyscale_HalfSingle_Blank20x20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f38baa9ef4e4fa2484a2036f2b03f0aed4f2d82b5ffd42cba01643a12552621c +size 240 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillWithCustomCenterColor.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillWithCustomCenterColor.png new file mode 100644 index 000000000..772cbc724 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillWithCustomCenterColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a7e58de9fec685980aecd0811d72c3ff37d15899ea8d9a216da589336f5f627 +size 186 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillWithCustomCenterColor_Rgba32_Blank10x10.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillWithCustomCenterColor_Rgba32_Blank10x10.png new file mode 100644 index 000000000..772cbc724 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushFillWithCustomCenterColor_Rgba32_Blank10x10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a7e58de9fec685980aecd0811d72c3ff37d15899ea8d9a216da589336f5f627 +size 186 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushShouldRotateTheColorsWhenThereAreMorePoints.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushShouldRotateTheColorsWhenThereAreMorePoints.png new file mode 100644 index 000000000..435275e21 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushShouldRotateTheColorsWhenThereAreMorePoints.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aeeeac9e3bf6a5b633ffb537bcde7133017d56776c727450633c1df7dc6de737 +size 163 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushShouldRotateTheColorsWhenThereAreMorePoints_Rgba32_Blank10x10.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushShouldRotateTheColorsWhenThereAreMorePoints_Rgba32_Blank10x10.png new file mode 100644 index 000000000..435275e21 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathGradientBrushShouldRotateTheColorsWhenThereAreMorePoints_Rgba32_Blank10x10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aeeeac9e3bf6a5b633ffb537bcde7133017d56776c727450633c1df7dc6de737 +size 163 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathSVGArcs.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathSVGArcs.png new file mode 100644 index 000000000..c448c1eb5 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPathSVGArcs.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bde8bcf94782a81d9575b4c0bca1c20100080db2b57db41ff226c386e1a7bc24 +size 2599 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithBackwardDiagonal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithBackwardDiagonal.png new file mode 100644 index 000000000..cc8710ec3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithBackwardDiagonal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25f45451fe5c6898611cdd7504d5f68a419e3fe8e2614cc5da1b0022b6a8864e +size 103 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithBackwardDiagonalTransparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithBackwardDiagonalTransparent.png new file mode 100644 index 000000000..f7d162409 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithBackwardDiagonalTransparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01608bed44a5e936807fb77b44ba1d2f4bccd84efd0774d29145775521a90892 +size 103 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithForwardDiagonal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithForwardDiagonal.png new file mode 100644 index 000000000..bd35802bd --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithForwardDiagonal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0693514c8034ecc07a6eba1971b7a35343226d757fd66603c9e4d09864747b8a +size 102 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithForwardDiagonalTransparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithForwardDiagonalTransparent.png new file mode 100644 index 000000000..5af54eab3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithForwardDiagonalTransparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23882cab09040361405de161753c0ffe2f27f6d3160495edf3ebff25cc4ca4b8 +size 102 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithHorizontal.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithHorizontal.png new file mode 100644 index 000000000..3823ec42f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithHorizontal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87eeebbfd7a24863bf73aa42b544528f7928ff7dc80d698f7650aafa28487d85 +size 93 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithHorizontalTransparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithHorizontalTransparent.png new file mode 100644 index 000000000..66efecdea --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithHorizontalTransparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87b01b762fa99c54d5a294640344e559768b049e9d1954e2ccd5205f9fb82126 +size 93 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithMin.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithMin.png new file mode 100644 index 000000000..bb9d648d9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithMin.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b813035f11b0d3abc80360ad38ced07ec0961d0744e438c13650bf76185dfdb1 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithMinTransparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithMinTransparent.png new file mode 100644 index 000000000..2ce615085 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithMinTransparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1eced1a6acf836a5d8916585c13087c987d6c7ec070d020e2347dd06cdb33ae +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent10.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent10.png new file mode 100644 index 000000000..8fa401b3a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:595a11891dca2657e14cc2b906b810f6d6089f00f552193cc597066c5b5de43d +size 99 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent10Transparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent10Transparent.png new file mode 100644 index 000000000..731c7682f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent10Transparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d69e73b2793eef700ac9bea010e8f40d9c588ebd34428d4bb2505b3ebe91190 +size 99 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent20.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent20.png new file mode 100644 index 000000000..96553c88f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent20.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a34da0f8310f7d90c8d571d08502794f984d63d74f24e2e0e26a2bc1768b0315 +size 94 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent20Transparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent20Transparent.png new file mode 100644 index 000000000..fbca36055 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithPercent20Transparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80c73db0b7f4e6b76a56bcb893cfa65f7da0a113751c505d0b0953618bc1763e +size 94 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithVertical.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithVertical.png new file mode 100644 index 000000000..dc2a9cabc --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithVertical.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4c4ecd5a396025b3868859e2ddfcbdd7943b2494bf0b31c35d467097abccf96 +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithVerticalTransparent.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithVerticalTransparent.png new file mode 100644 index 000000000..4e26095c1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPatternBrushImageShouldBeFloodFilledWithVerticalTransparent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c624408bb46eacdd6795a4d6738315a457d74f6b3ee8ad24c87802fd58da1029 +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(EvenOdd).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(EvenOdd).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(EvenOdd).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(EvenOdd).png diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(Nonzero).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(NonZero).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(Nonzero).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(False)_IntersectionRule(NonZero).png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(EvenOdd).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(EvenOdd).png new file mode 100644 index 000000000..d029a873a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(EvenOdd).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33f8f7a7b8392bba9e4dc9202d7dd6b2d699d925dc6a369c72a574a5818f0921 +size 177 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(NonZero).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(NonZero).png new file mode 100644 index 000000000..d029a873a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Complex_Reverse(True)_IntersectionRule(NonZero).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33f8f7a7b8392bba9e4dc9202d7dd6b2d699d925dc6a369c72a574a5818f0921 +size 177 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Concave_Reverse(False).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Concave_Reverse(False).png new file mode 100644 index 000000000..b5a207f2c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Concave_Reverse(False).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931c4ccc31543101fdd1196b8784ee939b643477b4272213988d95ef47efe30d +size 223 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Concave_Reverse(True).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Concave_Reverse(True).png new file mode 100644 index 000000000..b5a207f2c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Concave_Reverse(True).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931c4ccc31543101fdd1196b8784ee939b643477b4272213988d95ef47efe30d +size 223 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(False)_IntersectionRule(EvenOdd).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(False)_IntersectionRule(EvenOdd).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(False)_IntersectionRule(EvenOdd).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(False)_IntersectionRule(EvenOdd).png diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(False)_IntersectionRule(Nonzero).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(False)_IntersectionRule(NonZero).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_EllipsePolygon_Reverse(False)_IntersectionRule(Nonzero).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(False)_IntersectionRule(NonZero).png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(True)_IntersectionRule(EvenOdd).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(True)_IntersectionRule(EvenOdd).png new file mode 100644 index 000000000..262b726c3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(True)_IntersectionRule(EvenOdd).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80b6836e8d935831e9fdb2b387c9b395c6c6ebcf45aaeaee5fcda788fd13a241 +size 2639 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(True)_IntersectionRule(NonZero).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(True)_IntersectionRule(NonZero).png new file mode 100644 index 000000000..262b726c3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_EllipsePolygon_Reverse(True)_IntersectionRule(NonZero).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80b6836e8d935831e9fdb2b387c9b395c6c6ebcf45aaeaee5fcda788fd13a241 +size 2639 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rect_Rgba32_Car_rect.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rect_Rgba32_Car_rect.png new file mode 100644 index 000000000..97cf16382 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rect_Rgba32_Car_rect.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:404640208415ee934115ba2d4682a27b3b8788c5ec5687cbe6b14bc952a60a29 +size 38463 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rect_Rgba32_ducky_rect.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rect_Rgba32_ducky_rect.png new file mode 100644 index 000000000..996e13f38 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rect_Rgba32_ducky_rect.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67182aea50200bf85bfcf29cc12fe96803293e88b92be1bd0c12db75095ed2af +size 20647 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rgba32_Car.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rgba32_Car.png new file mode 100644 index 000000000..0d399af32 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rgba32_Car.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50554d4dedc99388aa93249ab0e29eb1c4cddd056096e9d52fa90ee31d6b34e7 +size 13418 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rgba32_ducky.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rgba32_ducky.png new file mode 100644 index 000000000..e3d781f7a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_ImageBrush_Rgba32_ducky.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fa77d6df6eb442520de655573e81f95db583a7f04603d6cb55357ef09d39a80 +size 17099 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_IntersectionRules_Nonzero.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_IntersectionRules_Nonzero.png new file mode 100644 index 000000000..92928f27e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_IntersectionRules_Nonzero.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce3f3b0f2bf919cf64fd1b01b0be62df81a0ba8221124ee37347a786a60dbd2c +size 110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_IntersectionRules_OddEven.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_IntersectionRules_OddEven.png new file mode 100644 index 000000000..d2bec2891 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_IntersectionRules_OddEven.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7ede75d604ad556527a04488a06085cdba2c4a044a60844a02dcf0c07a6eb58 +size 110 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Pattern_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Pattern_Rgba32.png new file mode 100644 index 000000000..5aacd9ffe --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Pattern_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:920da31b03dedb1b761ace43694fcd919d4c2185d7f2656da054c34e67e0180b +size 1634 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RectangularPolygon_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RectangularPolygon_Rgba32.png new file mode 100644 index 000000000..43bc50298 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RectangularPolygon_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82278e1de2e0079b3db5f1d9da3939c725081dd7d791d3e9cef2ef9de0f7aef7 +size 268 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RectangularPolygon_Solid_TransformedUsingConfiguration_Rgba32_BasicTestPattern100x100.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RectangularPolygon_Solid_TransformedUsingConfiguration_Rgba32_BasicTestPattern100x100.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RectangularPolygon_Solid_TransformedUsingConfiguration_Rgba32_BasicTestPattern100x100.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RectangularPolygon_Solid_TransformedUsingConfiguration_Rgba32_BasicTestPattern100x100.png diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RectangularPolygon_Solid_Transformed_Rgba32_BasicTestPattern100x100.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RectangularPolygon_Solid_Transformed_Rgba32_BasicTestPattern100x100.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/Fill_RectangularPolygon_Solid_Transformed_Rgba32_BasicTestPattern100x100.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RectangularPolygon_Solid_Transformed_Rgba32_BasicTestPattern100x100.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(50)_Ang(0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(50)_Ang(0).png new file mode 100644 index 000000000..3bbe70b6a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(50)_Ang(0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:171bd67abc692b7691b37ca1b377961cf0310aaf3353ff7bee09c5cf70de11b7 +size 1757 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(60)_Ang(-180).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(60)_Ang(-180).png new file mode 100644 index 000000000..ea5ff7af2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(60)_Ang(-180).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12cf2ba0107edcc9e54718ba760bfe5c64c6e34279fe7c906a4cbfb044a89775 +size 2012 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(60)_Ang(20).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(60)_Ang(20).png new file mode 100644 index 000000000..c8d180a07 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(3)_R(60)_Ang(20).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0dd9c3584229f6e9b488e4e63474a059c082b89c26609cca00769a53056a598 +size 2518 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(5)_R(70)_Ang(0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(5)_R(70)_Ang(0).png new file mode 100644 index 000000000..8bc1b3d99 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(5)_R(70)_Ang(0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45d30617520b3153f72457ca3cc9ddc8f930427b702105f2ad3d2c05c7579203 +size 2848 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(7)_R(80)_Ang(-180).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(7)_R(80)_Ang(-180).png new file mode 100644 index 000000000..b2cd63278 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_RegularPolygon_V(7)_R(80)_Ang(-180).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:889ffce3febe8de6ceca2ac67bbc41a2011d2e2104ab0f7e0a9e7401bd96dc5a +size 3258 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Basic_aa0.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Basic_aa0.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Basic_aa0.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Basic_aa0.png diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Basic_aa16.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Basic_aa16.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Basic_aa16.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Basic_aa16.png diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Basic_aa8.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Basic_aa8.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Basic_aa8.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Basic_aa8.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Bgr24_Yellow_A1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Bgr24_Yellow_A1.png new file mode 100644 index 000000000..6938b2f2c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Bgr24_Yellow_A1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96e62fdc1c5505eff8e68b691e2d9514049dcb4fd1b971577d929e210876afa8 +size 2451 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Rgba32_White_A0.6.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Rgba32_White_A0.6.png new file mode 100644 index 000000000..7effefd17 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Rgba32_White_A0.6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ac308624980962df3442cf2542ed90962083afc05eeef02e528b5499e308a26 +size 2521 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Rgba32_White_A1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Rgba32_White_A1.png new file mode 100644 index 000000000..8e2493ed5 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Rgba32_White_A1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d95a4146084f030ed33fe46a564010d699a9d412a62bfab8ec7866c722d780fa +size 2529 diff --git a/tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Rgba32_White_A1_NoAntialias.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Rgba32_White_A1_NoAntialias.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/FillPolygonTests/FillPolygon_Solid_Rgba32_White_A1_NoAntialias.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Rgba32_White_A1_NoAntialias.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Transformed_Rgba32_BasicTestPattern250x350.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Transformed_Rgba32_BasicTestPattern250x350.png new file mode 100644 index 000000000..394b09302 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_Solid_Transformed_Rgba32_BasicTestPattern250x350.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbe37bed60d319bb0f9f9b953d8872635b30074f384ae2ea8c33159d7ee7e931 +size 5418 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle.png new file mode 100644 index 000000000..debdd636d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f1615fbe0f7e8a309d26da49eb1d62661b66b1f5dccc20766d539ca406ee946 +size 1010 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Difference.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Difference.png new file mode 100644 index 000000000..add0c5018 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Difference.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2dfcfe9f8e155a6144b67bd4815c3f20ad34003c1db1437027da4f9d30e59d0 +size 2878 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Intersection.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Intersection.png new file mode 100644 index 000000000..d7732392d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Intersection.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51f32a7e3fea8df51451d5ebcca9322624cf78a9d858a602443a92ab9daf9bf8 +size 2985 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Union.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Union.png new file mode 100644 index 000000000..76fa75439 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Union.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9c4fd1beb18f332de4e5c7f2f2acaa66896ab26ff0f987c5f568702daad9dc7 +size 1586 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Xor.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Xor.png new file mode 100644 index 000000000..00879ae12 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillPolygon_StarCircle_AllOperations_Xor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b1df5961ad58ee84780740fafd495c60dccec0f3662c4af90fd019251cf444e +size 2868 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(-40,100).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(-40,100).png new file mode 100644 index 000000000..50e6559e0 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(-40,100).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c92da609a4c66a3775d65fded2472d3ed3a72c5af1fde8c1148e19d0f3b36346 +size 2134 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(0,0).png new file mode 100644 index 000000000..c6d01e640 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(0,0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b39304d8ce4df9a5f741f6336486b27e78cf80de07d3cb5fd56d9d2b75c1afd +size 1912 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(0,100).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(0,100).png new file mode 100644 index 000000000..ccd515477 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(0,100).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a30fff36046c0b58859f102bcc22b8ff90fd2c19ee71a8814b1c14c1b0032f3 +size 3355 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(100,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(100,0).png new file mode 100644 index 000000000..29f91d6ed --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(100,0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d27da50076609e7c215fa566e09d88ca98abef487d742870801a056c126d306 +size 3021 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(100,100).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(100,100).png new file mode 100644 index 000000000..b6b082da6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithDifferentCentersReturnsImage_center(100,100).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7657e38e341c96ede6e80e929eb1d22dafa04f19692a929a1cf1f4d535a0d889 +size 5171 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithEqualColorsReturnsUnicolorImage.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithEqualColorsReturnsUnicolorImage.png new file mode 100644 index 000000000..2234ebc2f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillRadialGradientBrushWithEqualColorsReturnsUnicolorImage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:672f2ab9f185757958192d8c28f94a800706a4f1ad3cfadc042443cac04056f1 +size 100 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png new file mode 100644 index 000000000..8d329b435 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:076baf174febccc2a3924c2f891d8486ee8c25c38978ae591261df69ee08e49e +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png new file mode 100644 index 000000000..918d647a3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2daedacd9a16eb276da4fabf41447e568d133a239d69cf7bfa0d1a7132d41e90 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png new file mode 100644 index 000000000..345a61049 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:474135f94f16c3a874b0b8a69b1f244224fa1b3bcbb5fad084eb1dc6eb3bb064 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png new file mode 100644 index 000000000..8d329b435 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:076baf174febccc2a3924c2f891d8486ee8c25c38978ae591261df69ee08e49e +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png new file mode 100644 index 000000000..ee5805b46 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd5940c044f15bb6ab4ee9b60e7e11d8d587bba9b510958ac8d0bd1e924c7a21 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png new file mode 100644 index 000000000..1a354b3ce --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83195d69738bf815d169ac86a0dda8fc3996ed78d46b27c22e572efa64d3a65e +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png new file mode 100644 index 000000000..3d04f2ab3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:683565c4a7dd7a20d60e11cf22ba7c178bec285025868d87a9cab7cddbd667d4 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png new file mode 100644 index 000000000..6f2a37c7b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:609f9c6c4fafc8babda5eb4ae5155d1b83e1527ec2af495d82e3ce721c2c9e41 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png new file mode 100644 index 000000000..f1ead9c08 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c2e607ec3da2e0c7f3ad1085f7394730246381a2b707c006662dc334871c03d +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png new file mode 100644 index 000000000..c57c20a83 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2200755515c19e5fea5040d1116a484c83b1e8e8545875118352fa4e087165e4 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png new file mode 100644 index 000000000..d4f6ee246 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d4780e3b175e966618f9d0ba070a6bcc8e8feebfb2be4c572870ad31b61e7c1 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png new file mode 100644 index 000000000..c57c20a83 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-False_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2200755515c19e5fea5040d1116a484c83b1e8e8545875118352fa4e087165e4 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png new file mode 100644 index 000000000..8d329b435 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Add_blendPercentage-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:076baf174febccc2a3924c2f891d8486ee8c25c38978ae591261df69ee08e49e +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png new file mode 100644 index 000000000..918d647a3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Multiply_blendPercentage-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2daedacd9a16eb276da4fabf41447e568d133a239d69cf7bfa0d1a7132d41e90 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png new file mode 100644 index 000000000..345a61049 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-0.5_blenderMode-Normal_blendPercentage-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:474135f94f16c3a874b0b8a69b1f244224fa1b3bcbb5fad084eb1dc6eb3bb064 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png new file mode 100644 index 000000000..8d329b435 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Add_blendPercentage-0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:076baf174febccc2a3924c2f891d8486ee8c25c38978ae591261df69ee08e49e +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png new file mode 100644 index 000000000..ee5805b46 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Multiply_blendPercentage-0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd5940c044f15bb6ab4ee9b60e7e11d8d587bba9b510958ac8d0bd1e924c7a21 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png new file mode 100644 index 000000000..1a354b3ce --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Blue_alpha-1_blenderMode-Normal_blendPercentage-0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83195d69738bf815d169ac86a0dda8fc3996ed78d46b27c22e572efa64d3a65e +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png new file mode 100644 index 000000000..3d04f2ab3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Add_blendPercentage-0.3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:683565c4a7dd7a20d60e11cf22ba7c178bec285025868d87a9cab7cddbd667d4 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png new file mode 100644 index 000000000..6f2a37c7b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Multiply_blendPercentage-0.3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:609f9c6c4fafc8babda5eb4ae5155d1b83e1527ec2af495d82e3ce721c2c9e41 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png new file mode 100644 index 000000000..f1ead9c08 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-Green_alpha-0.5_blenderMode-Normal_blendPercentage-0.3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c2e607ec3da2e0c7f3ad1085f7394730246381a2b707c006662dc334871c03d +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png new file mode 100644 index 000000000..c57c20a83 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Add_blendPercentage-0.8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2200755515c19e5fea5040d1116a484c83b1e8e8545875118352fa4e087165e4 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png new file mode 100644 index 000000000..d4f6ee246 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Multiply_blendPercentage-0.8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d4780e3b175e966618f9d0ba070a6bcc8e8feebfb2be4c572870ad31b61e7c1 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png new file mode 100644 index 000000000..c57c20a83 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_BlendFillColorOverBackground_triggerFillRegion-True_newColorName-HotPink_alpha-0.8_blenderMode-Normal_blendPercentage-0.8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2200755515c19e5fea5040d1116a484c83b1e8e8545875118352fa4e087165e4 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_Argb32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_Argb32.png new file mode 100644 index 000000000..662dd0037 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_Argb32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25adfbb17267acb56770cf0de92eab85bdb4c6a3bc790b24022b618e64e70f0f +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_Rgba32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_Rgba32.png new file mode 100644 index 000000000..662dd0037 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25adfbb17267acb56770cf0de92eab85bdb4c6a3bc790b24022b618e64e70f0f +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_RgbaVector.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_RgbaVector.png new file mode 100644 index 000000000..662dd0037 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSinglePixelType_RgbaVector.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25adfbb17267acb56770cf0de92eab85bdb4c6a3bc790b24022b618e64e70f0f +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank16x7.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank16x7.png new file mode 100644 index 000000000..113c9e069 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank16x7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:577cff471034b801e84c2df271946e59e441d0890910b949dcd7b81b25f38d58 +size 82 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank1x1.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank1x1.png new file mode 100644 index 000000000..d406a3275 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank1x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8df185b0b10595bba92b871646a6b349b308221e63c2ead096e718676716bddd +size 72 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank33x32.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank33x32.png new file mode 100644 index 000000000..4c6092dfc --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank33x32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1e7483e76c3b65b94b68499f93f07b2c73435353adf46638c2d1fe16d62f6a0 +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank400x500.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank400x500.png new file mode 100644 index 000000000..af764cb13 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank400x500.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed2eeed8c081a355f23062a56ea80f1891745c63f8468d65771e49418b508cd6 +size 119 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank7x4.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank7x4.png new file mode 100644 index 000000000..0a126de11 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_DoesNotDependOnSize_Blank7x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c24b47a3afd5d7185d9722cd8f0bd4274489bedd4e09c8d23a66e49af405dc2 +size 91 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png new file mode 100644 index 000000000..cf2790c36 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b99d68b7a4004b690bf3e2c03d408c40491926435fd1afdccd93ad23c919b20 +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png new file mode 100644 index 000000000..7631eab46 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4db5130b5c73181a950b9f3f4697a09d9486dba90fa140ace97c368c1e8550f +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png new file mode 100644 index 000000000..cf2790c36 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x5,y7,w3,h8).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b99d68b7a4004b690bf3e2c03d408c40491926435fd1afdccd93ad23c919b20 +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png new file mode 100644 index 000000000..7631eab46 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_Region_WorksOnWrappedMemoryImage_Rgba32_Solid16x16_(255,0,0,255)_(x8,y5,w6,h4).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4db5130b5c73181a950b9f3f4697a09d9486dba90fa140ace97c368c1e8550f +size 90 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_WhenColorIsOpaque_OverridePreviousColor_Blue.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_WhenColorIsOpaque_OverridePreviousColor_Blue.png new file mode 100644 index 000000000..8570310b7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_WhenColorIsOpaque_OverridePreviousColor_Blue.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c70c09a82dfbb4db1955e417c1f24ea90178f5234bba420e71f74a094218c7c +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_WhenColorIsOpaque_OverridePreviousColor_Khaki.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_WhenColorIsOpaque_OverridePreviousColor_Khaki.png new file mode 100644 index 000000000..3730d9c35 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSolidBrush_WhenColorIsOpaque_OverridePreviousColor_Khaki.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dfc380297d61413eec1b9a3f634ae0489c22fb310b004e1ecf6ea26ecc28b5f +size 83 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(0,end360).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(0,end360).png new file mode 100644 index 000000000..0878f254f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(0,end360).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b321256dc4d6b6a12a7ddc7b40151ca7c602e763c88e7170aa056ac48d84b203 +size 10561 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(180,end540).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(180,end540).png new file mode 100644 index 000000000..509f92aa2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(180,end540).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:daacb570dc72db1e98f2c0f9d7ba6463c4bb0eccb5d91daf7e68f2e5ea049967 +size 10788 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(270,end630).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(270,end630).png new file mode 100644 index 000000000..3677ec835 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(270,end630).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4d3158afdf03dd6e8084f26e506fafeb7eaf1e6d257b78446462d92e79a0fa4 +size 10641 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(90,end450).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(90,end450).png new file mode 100644 index 000000000..ab37337d6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FillSweepGradientBrush_RendersFullSweep_Every90Degrees_start(90,end450).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a28966be8af741d960f99575333a6a7a7878782ee241a92f9d9d1bfb7277f50 +size 10436 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png new file mode 100644 index 000000000..5ef307a54 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f476d97fd14ff7ad932548229ef81ac0266180a312ece122fda9a534b8e72b8e +size 18165 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png new file mode 100644 index 000000000..6464b040b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce7300e886038d4dae1e9b164ee455df2d51d3d367034de3050bfa0d7b0dc36a +size 776 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png new file mode 100644 index 000000000..68302ea94 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a6e8fd0ecc151ab8f6d3000904967f5d2cdcd011c5f7478b974ceeef6395769 +size 17554 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png new file mode 100644 index 000000000..e3435a9d1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d97aed86bc20ce47dd11ed7ca95c05c6950c59de375b25763c52f44834826eb +size 15485 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png new file mode 100644 index 000000000..6366790f1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43cd0f06711e590857c4f11a35dccde09edcbb9187e7df2741d9560ae263ef85 +size 728 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png new file mode 100644 index 000000000..e6a78aee9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectlyWithAPen_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba9405b6c8e73699185dd0ad75b560b4634faabf2c2cf5df2ce5f6834b984718 +size 15774 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_LargeText.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_LargeText.png new file mode 100644 index 000000000..3f6fa2cf9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_LargeText.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:427f884c9c1dad119bd8478a353796b55646dbad64f23ce211bf2482cee31cda +size 115461 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid1100x200_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(150,50).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid1100x200_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(150,50).png new file mode 100644 index 000000000..84d9e5390 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid1100x200_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(150,50).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:740c8f52dad954ac302f1c88155c1c41e0219fd4a93d9c7f5e00542260d7b8be +size 11166 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid200x150_(255,255,255,255)_SixLaborsSampleAB.woff-50-ABAB-(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid200x150_(255,255,255,255)_SixLaborsSampleAB.woff-50-ABAB-(0,0).png new file mode 100644 index 000000000..ab6e2d683 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid200x150_(255,255,255,255)_SixLaborsSampleAB.woff-50-ABAB-(0,0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e347dfb9591f3db40f5692ee7ade9b410784eb06723c92558f5b275382c826b6 +size 605 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid20x50_(255,255,255,255)_OpenSans-Regular.ttf-50-i-(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid20x50_(255,255,255,255)_OpenSans-Regular.ttf-50-i-(0,0).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid20x50_(255,255,255,255)_OpenSans-Regular.ttf-50-i-(0,0).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid20x50_(255,255,255,255)_OpenSans-Regular.ttf-50-i-(0,0).png diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid400x45_(255,255,255,255)_OpenSans-Regular.ttf-20-Sphi-(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid400x45_(255,255,255,255)_OpenSans-Regular.ttf-20-Sphi-(0,0).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid400x45_(255,255,255,255)_OpenSans-Regular.ttf-20-Sphi-(0,0).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid400x45_(255,255,255,255)_OpenSans-Regular.ttf-20-Sphi-(0,0).png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid900x150_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(0,0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid900x150_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(0,0).png new file mode 100644 index 000000000..130361f99 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_Solid900x150_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(0,0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f788f1e86060b61c7d52b3c0efdd654e66a2fdc5fc90e68a76f6018ef9cd9f8a +size 10604 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1.5_linecount_3_wrap_False.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1.5_linecount_3_wrap_False.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1.5_linecount_3_wrap_False.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1.5_linecount_3_wrap_False.png diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1.5_linecount_3_wrap_True.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1.5_linecount_3_wrap_True.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1.5_linecount_3_wrap_True.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1.5_linecount_3_wrap_True.png diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1_linecount_5_wrap_False.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1_linecount_5_wrap_False.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1_linecount_5_wrap_False.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1_linecount_5_wrap_False.png diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1_linecount_5_wrap_True.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1_linecount_5_wrap_True.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1_linecount_5_wrap_True.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_1_linecount_5_wrap_True.png diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_2_linecount_2_wrap_False.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_2_linecount_2_wrap_False.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_2_linecount_2_wrap_False.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_2_linecount_2_wrap_False.png diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_2_linecount_2_wrap_True.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_2_linecount_2_wrap_True.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_2_linecount_2_wrap_True.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithLineSpacing_linespacing_2_linecount_2_wrap_True.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-Sphi-(550,550).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-Sphi-(550,550).png new file mode 100644 index 000000000..1cf570754 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-Sphi-(550,550).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b62d4300ed2d7cdc7215c554200f6cd3b96daaa00b00de7a7b2228babe90e69 +size 15834 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(45)-ABAB-(100,100).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(45)-ABAB-(100,100).png new file mode 100644 index 000000000..629502a16 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(45)-ABAB-(100,100).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be46a8d83553144fac730baecd57326e47af2ff18b80cdcfcc8029ae9132115d +size 1082 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(45)-Sphi-(200,200).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(45)-Sphi-(200,200).png new file mode 100644 index 000000000..bbf462234 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(45)-Sphi-(200,200).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43826aabef30a85430692d0d8f2d391f7b7c51c2f748531089cdd19c0347aef3 +size 5233 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-i-(25,25).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-i-(25,25).png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-i-(25,25).png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-i-(25,25).png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(0,10)-Sphi-(550,550).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(0,10)-Sphi-(550,550).png new file mode 100644 index 000000000..dd73076e9 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(0,10)-Sphi-(550,550).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81de087ca73048b8a8063887fafa049b151d5eb819962447ae7e93a10276053f +size 14684 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(10,0)-ABAB-(100,100).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(10,0)-ABAB-(100,100).png new file mode 100644 index 000000000..eba451cd6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(10,0)-ABAB-(100,100).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4923300182d4227bf8f285c47a09e7544d39acdd629076d6de9830a47d3f3b8b +size 1000 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(0,-10)-Sphi-(200,200).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(0,-10)-Sphi-(200,200).png new file mode 100644 index 000000000..1aa15bfc1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(0,-10)-Sphi-(200,200).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a40e171a28a3620bb582d30be4281ddabd94f11302f65aa63ab7d4ab4e7f7ca5 +size 5021 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(-12,0)-i-(25,25).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(-12,0)-i-(25,25).png new file mode 100644 index 000000000..44e996e9b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(-12,0)-i-(25,25).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2048b8b8ff6435fbd32f4966cb2a15edd9f32d59a3faccef05333da08f9040a3 +size 293 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(10).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(10).png new file mode 100644 index 000000000..62a8f02ea --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(10).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eab1ca9869fb7930ee5bee6ecfbfe4692b7eeb009f2d0323132896356ec79fe4 +size 80947 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(3).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(3).png new file mode 100644 index 000000000..5217fcd88 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(3).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ab1ec5f1606225bad6517c9f7ac54a48ce96b9cec86793e99a31e054a06fcbe +size 18147 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(5).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(5).png new file mode 100644 index 000000000..7fb314d05 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_LinesScaled_Scale(5).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e15dd4e0a7d46d3877c3848b19a61618fb7734041c7ccdde5dceb22e6408aa07 +size 34735 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_Lines_PixelOffset(0).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_Lines_PixelOffset(0).png new file mode 100644 index 000000000..7095cea88 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_Lines_PixelOffset(0).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:512696c1aca0a9804434a7e46a6b5b3efab688ef7c9d22fa4657a7a8cb6ea90d +size 4588 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_Lines_PixelOffset(5500).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_Lines_PixelOffset(5500).png new file mode 100644 index 000000000..112d017cd --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_Mississippi_Lines_PixelOffset(5500).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc4989cf7a88c1f07305649bf1809646ed116be2849fe0f0aad2e16a33f53f69 +size 41159 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_States_Fill.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_States_Fill.png new file mode 100644 index 000000000..e517a34a8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/LargeGeoJson_States_Fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43d49cdfb9c602789452dba4b6c02b2b1c2c0517f10d19aed7ace0a9e9e96bd5 +size 407764 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/PathAndTextDrawingMatch_Rgba32_Solid1000x1000_(255,255,255,255).png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/PathAndTextDrawingMatch_Rgba32_Solid1000x1000_(255,255,255,255).png new file mode 100644 index 000000000..6fbc1665c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/PathAndTextDrawingMatch_Rgba32_Solid1000x1000_(255,255,255,255).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0703eb4a553d757637c38858c3115dbe5462c391288ef271cdb53fef211a096d +size 36248 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png new file mode 100644 index 000000000..997b6720d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:453db170043d15c55302819678b2dd43686388196f9307e254a1e9796e247fae +size 306360 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_InBox_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_InBox_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png new file mode 100644 index 000000000..1b7e4c6bf --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_InBox_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:608e0ce36b4866c077cb025e5eca78b8e85e4a5b9f2fa4459e97a410f27928cd +size 313445 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_InBox_Rgba32_TestPattern100x100_Red-Blue-0.2.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_InBox_Rgba32_TestPattern100x100_Red-Blue-0.2.png new file mode 100644 index 000000000..265e0f2be --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_InBox_Rgba32_TestPattern100x100_Red-Blue-0.2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:553b92cb26ffb752d7ada262c7d918c840a6ca8b91f480714ced7a751259e89a +size 3329 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_CalliphoraPartial_Yellow-Pink-0.2.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_CalliphoraPartial_Yellow-Pink-0.2.png new file mode 100644 index 000000000..925389091 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_CalliphoraPartial_Yellow-Pink-0.2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5db7f7974d1d83cea37e88cb7cdb1482563c0264b5dd3b9d3d0f971b8a6c5283 +size 325228 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_TestPattern100x100_Red-Blue-0.2.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_TestPattern100x100_Red-Blue-0.2.png new file mode 100644 index 000000000..c48efa24f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_TestPattern100x100_Red-Blue-0.2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2aadb60a463ce878e6f6d03ab44fa8b1ce1f29f166e2ec9619f50b4dfd00ebe +size 4964 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_TestPattern100x100_Red-Blue-0.6.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_TestPattern100x100_Red-Blue-0.6.png new file mode 100644 index 000000000..e07ff4945 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/RecolorImage_Rgba32_TestPattern100x100_Red-Blue-0.6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b85840ba2c6c185bc7f979a9e889aae74d5ae4781cc3915e817105f6a6af235f +size 10085 diff --git a/tests/Images/ReferenceOutput/Drawing/SolidBezierTests/FilledBezier_Rgba32_Blank500x500.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SolidBezierFilledBezier_Rgba32_Blank500x500.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidBezierTests/FilledBezier_Rgba32_Blank500x500.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SolidBezierFilledBezier_Rgba32_Blank500x500.png diff --git a/tests/Images/ReferenceOutput/Drawing/SolidBezierTests/OverlayByFilledPolygonOpacity_Rgba32_Blank500x500.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SolidBezierOverlayByFilledPolygonOpacity_Rgba32_Blank500x500.png similarity index 100% rename from tests/Images/ReferenceOutput/Drawing/SolidBezierTests/OverlayByFilledPolygonOpacity_Rgba32_Blank500x500.png rename to tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SolidBezierOverlayByFilledPolygonOpacity_Rgba32_Blank500x500.png diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank100x100_type-arrows.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank100x100_type-arrows.png new file mode 100644 index 000000000..0e6e38803 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank100x100_type-arrows.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52f5870479d19eedd37cc31a7a7b25fc3285f6466d524d46ecf85f85fa0f9e6d +size 393 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank110x50_type-wave.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank110x50_type-wave.png new file mode 100644 index 000000000..db9f082e7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank110x50_type-wave.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:055c34ce5f5a1dc3c0ea4f806a3e4cf0ceae938a71ebd52564d6bb2fa7d4aeb5 +size 655 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank110x70_type-zag.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank110x70_type-zag.png new file mode 100644 index 000000000..175041966 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank110x70_type-zag.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99d520aa8a1feb8abd21b63c8cb6f105383742830a3ebc3ca9c8b3deb64ba4ef +size 436 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-bumpy.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-bumpy.png new file mode 100644 index 000000000..c9f160d53 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-bumpy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ea2dbafb56496657590e59a81f09446cb18b8573740e83ad80fb910b18e5017 +size 4847 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-chopped_oval.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-chopped_oval.png new file mode 100644 index 000000000..47e8c16ea --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-chopped_oval.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b52f21326c9ebeb3429dc5f6a4e9a99bb33573da31be9582baa8ce1212c47387 +size 2769 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-pie_big.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-pie_big.png new file mode 100644 index 000000000..e1f9b87cb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-pie_big.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09f2f38f2b595182dd24a0772c34c45567d8659a478132e97771cadea2c34b9c +size 2542 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-pie_small.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-pie_small.png new file mode 100644 index 000000000..3425de7aa --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/SvgPathRenderSvgPath_Rgba32_Blank500x400_type-pie_small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa2f4adac7cd072ffb308ffa89392e3dfce3c41f67352a1701fff426a3d07c9e +size 4848 diff --git a/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/TextPositioningIsRobust_OpenSans-Regular.ttf.png b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/TextPositioningIsRobust_OpenSans-Regular.ttf.png new file mode 100644 index 000000000..0b2bf7e41 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/ProcessWithDrawingCanvasTests/TextPositioningIsRobust_OpenSans-Regular.ttf.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c6bf01e2e38c30fc1a12d208d6264d43e6359fc202de37ce137479738720084 +size 184460 diff --git a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png b/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png deleted file mode 100644 index 0d763c0f2..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_InBox_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png b/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_InBox_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png deleted file mode 100644 index 87f594747..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_InBox_Bgra32_CalliphoraPartial_Yellow-Pink-0.5.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_InBox_Rgba32_TestPattern100x100_Red-Blue-0.2.png b/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_InBox_Rgba32_TestPattern100x100_Red-Blue-0.2.png deleted file mode 100644 index ea1921837..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_InBox_Rgba32_TestPattern100x100_Red-Blue-0.2.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_CalliphoraPartial_Yellow-Pink-0.2.png b/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_CalliphoraPartial_Yellow-Pink-0.2.png deleted file mode 100644 index 0472c7b96..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_CalliphoraPartial_Yellow-Pink-0.2.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_TestPattern100x100_Red-Blue-0.2.png b/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_TestPattern100x100_Red-Blue-0.2.png deleted file mode 100644 index 05ecc0ae5..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_TestPattern100x100_Red-Blue-0.2.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_TestPattern100x100_Red-Blue-0.6.png b/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_TestPattern100x100_Red-Blue-0.6.png deleted file mode 100644 index d653c37ad..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/RecolorImageTests/Recolor_Rgba32_TestPattern100x100_Red-Blue-0.6.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Add.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Darken.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-HardLight.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Lighten.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Multiply.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Normal.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Overlay.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Screen.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Subtract.png deleted file mode 100644 index c7ab88be3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Clear_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Add.png deleted file mode 100644 index a5c638505..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Darken.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-HardLight.png deleted file mode 100644 index 131d4dc8a..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Lighten.png deleted file mode 100644 index a5c638505..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Multiply.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Normal.png deleted file mode 100644 index a5c638505..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Overlay.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Screen.png deleted file mode 100644 index a5c638505..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Subtract.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestAtop_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Add.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Darken.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-HardLight.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Lighten.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Multiply.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Normal.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Overlay.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Screen.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Subtract.png deleted file mode 100644 index 438d11255..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestIn_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Add.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Darken.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-HardLight.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Lighten.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Multiply.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Normal.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Overlay.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Screen.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Subtract.png deleted file mode 100644 index aadf1c064..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOut_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Add.png deleted file mode 100644 index fdf747851..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Darken.png deleted file mode 100644 index 6589f4b4b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-HardLight.png deleted file mode 100644 index e98bf8cec..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Lighten.png deleted file mode 100644 index fdf747851..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Multiply.png deleted file mode 100644 index 6589f4b4b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Normal.png deleted file mode 100644 index fdf747851..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Overlay.png deleted file mode 100644 index 6589f4b4b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Screen.png deleted file mode 100644 index fdf747851..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Subtract.png deleted file mode 100644 index 6589f4b4b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-DestOver_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Add.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Darken.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-HardLight.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Lighten.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Multiply.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Normal.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Overlay.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Screen.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Subtract.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Dest_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Add.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Darken.png deleted file mode 100644 index e551a7302..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-HardLight.png deleted file mode 100644 index e551a7302..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Lighten.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Multiply.png deleted file mode 100644 index e551a7302..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Normal.png deleted file mode 100644 index e551a7302..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Overlay.png deleted file mode 100644 index ff8fddbe9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Screen.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Subtract.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcAtop_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Add.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Darken.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-HardLight.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Lighten.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Multiply.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Normal.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Overlay.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Screen.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Subtract.png deleted file mode 100644 index 6b582928b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcIn_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Add.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Darken.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-HardLight.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Lighten.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Multiply.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Normal.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Overlay.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Screen.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Subtract.png deleted file mode 100644 index e75c117cf..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOut_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Add.png deleted file mode 100644 index fdf747851..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Darken.png deleted file mode 100644 index 6589f4b4b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-HardLight.png deleted file mode 100644 index 6589f4b4b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Lighten.png deleted file mode 100644 index fdf747851..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Multiply.png deleted file mode 100644 index 6589f4b4b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Normal.png deleted file mode 100644 index 6589f4b4b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Overlay.png deleted file mode 100644 index e98bf8cec..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Screen.png deleted file mode 100644 index fdf747851..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Subtract.png deleted file mode 100644 index fdf747851..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-SrcOver_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Add.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Darken.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-HardLight.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Lighten.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Multiply.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Normal.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Overlay.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Screen.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Subtract.png deleted file mode 100644 index 0a489f8e3..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Src_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Add.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Darken.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-HardLight.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Lighten.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Multiply.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Normal.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Overlay.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Screen.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Subtract.png deleted file mode 100644 index c9286262f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendBlackEllipse_composition-Xor_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Add.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Darken.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-HardLight.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Lighten.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Multiply.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Normal.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Overlay.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Screen.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Subtract.png deleted file mode 100644 index 42f37898f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOut_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Add.png deleted file mode 100644 index 8996f8113..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Darken.png deleted file mode 100644 index 6ca91fea5..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-HardLight.png deleted file mode 100644 index 6a173a4d0..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Lighten.png deleted file mode 100644 index 7c43a67f7..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Multiply.png deleted file mode 100644 index dee124965..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Normal.png deleted file mode 100644 index 12cad227b..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Overlay.png deleted file mode 100644 index 49945e472..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Screen.png deleted file mode 100644 index 87c688054..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Subtract.png deleted file mode 100644 index 5f3f36f5c..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-DestOver_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Add.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Darken.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-HardLight.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Lighten.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Multiply.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Normal.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Overlay.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Screen.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Subtract.png deleted file mode 100644 index 80cfabaed..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Dest_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Add.png deleted file mode 100644 index cd97328f7..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Darken.png deleted file mode 100644 index 15bc914ae..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-HardLight.png deleted file mode 100644 index fed2801fc..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Lighten.png deleted file mode 100644 index aa91deacc..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Multiply.png deleted file mode 100644 index 9fdbbc176..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Normal.png deleted file mode 100644 index 35707ec0d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Overlay.png deleted file mode 100644 index 8eaad1641..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Screen.png deleted file mode 100644 index 3beabf2c1..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Subtract.png deleted file mode 100644 index ad9b2d08f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcAtop_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Add.png deleted file mode 100644 index 8996f8113..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Darken.png deleted file mode 100644 index 6ca91fea5..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-HardLight.png deleted file mode 100644 index 49945e472..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Lighten.png deleted file mode 100644 index 7c43a67f7..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Multiply.png deleted file mode 100644 index dee124965..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Normal.png deleted file mode 100644 index f45b1a903..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Overlay.png deleted file mode 100644 index 6a173a4d0..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Screen.png deleted file mode 100644 index 87c688054..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Subtract.png deleted file mode 100644 index 29cea214f..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-SrcOver_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Add.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Darken.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-HardLight.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Lighten.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Multiply.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Normal.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Overlay.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Screen.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Subtract.png deleted file mode 100644 index 8939c7b50..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse_composition-Xor_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Add.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Darken.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-HardLight.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Lighten.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Multiply.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Normal.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Overlay.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Screen.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Subtract.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOut_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Add.png deleted file mode 100644 index ade8cd764..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Darken.png deleted file mode 100644 index b4f433274..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-HardLight.png deleted file mode 100644 index 83c95a638..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Lighten.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Multiply.png deleted file mode 100644 index a46206b47..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Normal.png deleted file mode 100644 index b4f433274..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Overlay.png deleted file mode 100644 index 9aa2cb468..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Screen.png deleted file mode 100644 index 1fed1d6d0..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Subtract.png deleted file mode 100644 index 4e721e269..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-DestOver_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Add.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Darken.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-HardLight.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Lighten.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Multiply.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Normal.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Overlay.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Screen.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Subtract.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Dest_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Add.png deleted file mode 100644 index e022e6db2..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Darken.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-HardLight.png deleted file mode 100644 index de81940d8..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Lighten.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Multiply.png deleted file mode 100644 index 5f9ec76a7..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Normal.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Overlay.png deleted file mode 100644 index 5ffb0e92c..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Screen.png deleted file mode 100644 index 47e93cb39..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Subtract.png deleted file mode 100644 index 8e727d849..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcAtop_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Add.png deleted file mode 100644 index ade8cd764..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Darken.png deleted file mode 100644 index b4f433274..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-HardLight.png deleted file mode 100644 index 9aa2cb468..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Lighten.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Multiply.png deleted file mode 100644 index a46206b47..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Normal.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Overlay.png deleted file mode 100644 index 83c95a638..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Screen.png deleted file mode 100644 index 1fed1d6d0..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Subtract.png deleted file mode 100644 index 0843d42fd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-SrcOver_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Add.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Darken.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-HardLight.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Lighten.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Multiply.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Normal.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Overlay.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Screen.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Subtract.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse_composition-Xor_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Add.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Darken.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-HardLight.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Lighten.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Multiply.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Normal.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Overlay.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Screen.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Subtract.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Clear_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Add.png deleted file mode 100644 index ade8cd764..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Darken.png deleted file mode 100644 index b4f433274..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-HardLight.png deleted file mode 100644 index 83c95a638..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Lighten.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Multiply.png deleted file mode 100644 index a46206b47..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Normal.png deleted file mode 100644 index b4f433274..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Overlay.png deleted file mode 100644 index 9aa2cb468..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Screen.png deleted file mode 100644 index 1fed1d6d0..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Subtract.png deleted file mode 100644 index 4e721e269..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestAtop_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Add.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Darken.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-HardLight.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Lighten.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Multiply.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Normal.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Overlay.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Screen.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Subtract.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestIn_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Add.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Darken.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-HardLight.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Lighten.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Multiply.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Normal.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Overlay.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Screen.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Subtract.png deleted file mode 100644 index 8071681d6..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOut_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Add.png deleted file mode 100644 index ade8cd764..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Darken.png deleted file mode 100644 index b4f433274..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-HardLight.png deleted file mode 100644 index 83c95a638..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Lighten.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Multiply.png deleted file mode 100644 index a46206b47..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Normal.png deleted file mode 100644 index b4f433274..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Overlay.png deleted file mode 100644 index 9aa2cb468..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Screen.png deleted file mode 100644 index 1fed1d6d0..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Subtract.png deleted file mode 100644 index 4e721e269..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-DestOver_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Add.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Darken.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-HardLight.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Lighten.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Multiply.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Normal.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Overlay.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Screen.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Subtract.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Dest_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Add.png deleted file mode 100644 index e022e6db2..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Darken.png deleted file mode 100644 index b11a2e63d..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-HardLight.png deleted file mode 100644 index de81940d8..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Lighten.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Multiply.png deleted file mode 100644 index 5f9ec76a7..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Normal.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Overlay.png deleted file mode 100644 index 5ffb0e92c..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Screen.png deleted file mode 100644 index 47e93cb39..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Subtract.png deleted file mode 100644 index 8e727d849..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcAtop_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Add.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Darken.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-HardLight.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Lighten.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Multiply.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Normal.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Overlay.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Screen.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Subtract.png deleted file mode 100644 index ff14eccb9..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcIn_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Add.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Darken.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-HardLight.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Lighten.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Multiply.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Normal.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Overlay.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Screen.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Subtract.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOut_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Add.png deleted file mode 100644 index ade8cd764..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Darken.png deleted file mode 100644 index b4f433274..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-HardLight.png deleted file mode 100644 index 9aa2cb468..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Lighten.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Multiply.png deleted file mode 100644 index a46206b47..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Normal.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Overlay.png deleted file mode 100644 index 83c95a638..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Screen.png deleted file mode 100644 index 1fed1d6d0..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Subtract.png deleted file mode 100644 index 0843d42fd..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-SrcOver_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Add.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Darken.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-HardLight.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Lighten.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Multiply.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Normal.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Overlay.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Screen.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Subtract.png deleted file mode 100644 index 8db71d319..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Src_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Add.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Add.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Add.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Darken.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Darken.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Darken.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-HardLight.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-HardLight.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-HardLight.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Lighten.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Lighten.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Lighten.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Multiply.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Multiply.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Multiply.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Normal.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Normal.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Normal.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Overlay.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Overlay.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Overlay.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Screen.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Screen.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Screen.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Subtract.png b/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Subtract.png deleted file mode 100644 index 12e1335aa..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/SolidFillBlendedShapesTests/_1DarkBlueRect_2BlendHotPinkRect_composition-Xor_blending-Subtract.png and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank100x100_type-spiral.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank100x100_type-spiral.png deleted file mode 100644 index 5b5dc4529..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank100x100_type-spiral.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c50feec7f3eb4a9dde88462398c46af6841aa4f27bff943858be8219d03d31f -size 5299 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank120x120_type-triangle.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank120x120_type-triangle.png deleted file mode 100644 index 1ab954d5d..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank120x120_type-triangle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:70beac5ff86d52b20e44dc6426747949d8308fb756397f305fd50de303e0cd1b -size 4387 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank350x350_type-circle.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank350x350_type-circle.png deleted file mode 100644 index d2d64f8b5..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathHorizontal_Rgba32_Blank350x350_type-circle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abb325c92147f9810d04059a1ea24e6be9e7dd0471613a16df266371e25f6f10 -size 9390 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathVertical_Rgba32_Blank250x250_type-triangle.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathVertical_Rgba32_Blank250x250_type-triangle.png deleted file mode 100644 index 9e50b2fa0..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathVertical_Rgba32_Blank250x250_type-triangle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd551e861f821dd70f9a915957703b8c691ccf30a71159e32ff6d301c4c1a4fe -size 5181 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathVertical_Rgba32_Blank350x350_type-circle.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathVertical_Rgba32_Blank350x350_type-circle.png deleted file mode 100644 index dc6115863..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextAlongPathVertical_Rgba32_Blank350x350_type-circle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffe197264326acae59f95a1021f645c423b755f2e9feccc7d284a90c2e0a275f -size 7395 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVertical2_Rgba32_Blank48x935.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVertical2_Rgba32_Blank48x935.png deleted file mode 100644 index 1365aa909..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVertical2_Rgba32_Blank48x935.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6dec4a6f836b95b35dd6b4bfefed4a139faf399f5ee0429d2af6da0d659ccf6b -size 4985 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVerticalMixed2_Rgba32_Blank48x839.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVerticalMixed2_Rgba32_Blank48x839.png deleted file mode 100644 index 483091b77..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVerticalMixed2_Rgba32_Blank48x839.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d3593b23fc0f52360731271313e444175efbbe5a3fe9df0e01422bb66cd311d -size 4906 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVertical_Rgba32_Blank500x400.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVertical_Rgba32_Blank500x400.png deleted file mode 100644 index cb39952cb..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanDrawTextVertical_Rgba32_Blank500x400.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7957a4f6299912762624320746e36a691f14a40f1282b3333d742e44e041e016 -size 13580 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanFillTextVerticalMixed_Rgba32_Blank500x400.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanFillTextVerticalMixed_Rgba32_Blank500x400.png deleted file mode 100644 index ebebcb871..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanFillTextVerticalMixed_Rgba32_Blank500x400.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfb920a3e19a7b6a86e7c16f26f370d91819100b1e9b38052781bdde9bc90078 -size 10593 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanFillTextVertical_Rgba32_Blank500x400.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanFillTextVertical_Rgba32_Blank500x400.png deleted file mode 100644 index a9d95b2b6..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanFillTextVertical_Rgba32_Blank500x400.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb8c07ae7263cada6fde58146f84132c4fc725d18c96b699716bd468e3d0ae8a -size 5127 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRenderTextOutOfBoundsIssue301.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRenderTextOutOfBoundsIssue301.png deleted file mode 100644 index 2d7907dad..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRenderTextOutOfBoundsIssue301.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2438c3dc6c663a4e51f6c33370a79c0e1a5a659a0508ff2c3696838a183da19e -size 1133 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-Quic).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-Quic).png deleted file mode 100644 index 335809eec..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-Quic).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d906e2161a7e83a02fe103d37f7502f6364666f848963119f16e968ebaccaa59 -size 1960 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-Quic).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-Quic).png deleted file mode 100644 index 2b116b146..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateFilledFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-Quic).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5209d55719175ad95aa4af0ee7b91404c1f0870b0bbf5633d9b6a5041901a88 -size 1723 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-STR(1)-Quic).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-STR(1)-Quic).png deleted file mode 100644 index 12024df67..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(32)-A(75)-STR(1)-Quic).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9bfb1deffe74cd385e005130793fcfaeade200ad6de77348c7624cb66d742204 -size 2582 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-STR(2)-Quic).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-STR(2)-Quic).png deleted file mode 100644 index 9b8104f7c..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/CanRotateOutlineFont_Issue175_Solid300x200_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(40)-A(90)-STR(2)-Quic).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80f7a935cc93f5bbc0fa9b02b2f36c294f71204f9654d224540cf69805f68f05 -size 2501 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextArabic_Solid500x200_(0,0,0,255)_RichText-Arabic-F(32).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextArabic_Solid500x200_(0,0,0,255)_RichText-Arabic-F(32).png deleted file mode 100644 index d4e7c41a5..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextArabic_Solid500x200_(0,0,0,255)_RichText-Arabic-F(32).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:766844bcd409f83dd46ff5c0f2615bd9b31e3fa9719109d3127940508862715c -size 3119 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextArabic_Solid500x300_(0,0,0,255)_RichText-Arabic-F(40).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextArabic_Solid500x300_(0,0,0,255)_RichText-Arabic-F(40).png deleted file mode 100644 index 474f9fd69..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextArabic_Solid500x300_(0,0,0,255)_RichText-Arabic-F(40).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa3c5c1e9033618a4bef1b02427176991eb9b767b6570948b55c1067d70ff771 -size 3921 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextRainbow_Solid500x200_(0,0,0,255)_RichText-Rainbow-F(32).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextRainbow_Solid500x200_(0,0,0,255)_RichText-Rainbow-F(32).png deleted file mode 100644 index 58256c3f3..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextRainbow_Solid500x200_(0,0,0,255)_RichText-Rainbow-F(32).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff56241312753f433b55ac70ec8bc12b3f164ad24da212581b53c637cd1711fc -size 8675 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextRainbow_Solid500x300_(0,0,0,255)_RichText-Rainbow-F(40).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextRainbow_Solid500x300_(0,0,0,255)_RichText-Rainbow-F(40).png deleted file mode 100644 index e962adb7d..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichTextRainbow_Solid500x300_(0,0,0,255)_RichText-Rainbow-F(40).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99f3b08907243b9afa6ec004da2e013cfd82ded5e287e28b02b940b799aabaa2 -size 11445 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichText_Solid500x200_(0,0,0,255)_RichText-F(32).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichText_Solid500x200_(0,0,0,255)_RichText-F(32).png deleted file mode 100644 index 5120c4629..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichText_Solid500x200_(0,0,0,255)_RichText-F(32).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58c86318d4963c1841c18c1bf5b88a661427585ef0eee6fb9825d24fc2e64820 -size 9158 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichText_Solid500x300_(0,0,0,255)_RichText-F(40).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichText_Solid500x300_(0,0,0,255)_RichText-F(40).png deleted file mode 100644 index eb9103188..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/DrawRichText_Solid500x300_(0,0,0,255)_RichText-F(40).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:734ded4b3f5b6a42f5a38ff65efee9d8466e5511f8b7c2492f36250a0d0f615c -size 11792 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-False.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-False.png deleted file mode 100644 index f9714e303..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-False.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:809d47db52fe7c6248704e6c4edf257e06365da15bde62140175a3fee534ccba -size 10040 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-True.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-True.png deleted file mode 100644 index a09ecc748..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/EmojiFontRendering_Rgba32_Solid1276x336_(255,255,255,255)_ColorFontsEnabled-True.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1c1ab0671873d0ac224ef2303aacfbbec2acb2d914040ce2d5469e51fb5eea18 -size 18524 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FallbackFontRendering_Rgba32_Solid400x200_(255,255,255,255).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FallbackFontRendering_Rgba32_Solid400x200_(255,255,255,255).png deleted file mode 100644 index b8b94d90c..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FallbackFontRendering_Rgba32_Solid400x200_(255,255,255,255).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:927f376922e21e380fbd943ddf9a13f14774d4d3b7110436b82364fa1889671a -size 1794 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png deleted file mode 100644 index 8dad5340a..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6038e34918109e904806da6e70ada04a61db754784625b2572f75752fa521627 -size 17528 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png deleted file mode 100644 index 37e3bd5fb..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a541428859171c4d2e0d23d63fc916aea2c3f911333886d6f61fcc198feb19b0 -size 759 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png deleted file mode 100644 index 0aa68114b..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPenPatterned_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f6ec4b89aebe34fff668d656ff170ffee6c3a6b07d96eb3e414eb989bf21859 -size 16990 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png deleted file mode 100644 index 864ffbf1c..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid1100x200_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(150,50).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d618766c3826b46082f6c248205b51dc18e6f4f7a328f454cd085813ecb78a3c -size 15084 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png deleted file mode 100644 index 12ac94d02..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid200x150_(255,255,255,255)_pen_SixLaborsSampleAB.woff-50-ABAB-(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e26c9ceae90a42180b573f97da0ce2b12e4ef30b3043bcee014e24d227913be -size 706 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png deleted file mode 100644 index d839ae8e1..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectlyWithAPen_Solid900x150_(255,255,255,255)_pen_OpenSans-Regular.ttf-50-Sphi-(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4e91bd745be89a8d9126e5a9c73e0f62f286db3be7080545c80fef3ec19da177 -size 15452 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_LargeText.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_LargeText.png deleted file mode 100644 index 9780a7767..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_LargeText.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2911ef69f673be85d75ca8b70f4039823290fdc3096caa0ef792d541bd531b9f -size 115331 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid1100x200_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(150,50).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid1100x200_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(150,50).png deleted file mode 100644 index cfd648192..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid1100x200_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(150,50).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae6ee08cb58592e49582e3543f99beb955e0e343a874af053098949cef1e25d8 -size 11040 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid200x150_(255,255,255,255)_SixLaborsSampleAB.woff-50-ABAB-(0,0).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid200x150_(255,255,255,255)_SixLaborsSampleAB.woff-50-ABAB-(0,0).png deleted file mode 100644 index 3e68f9b77..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid200x150_(255,255,255,255)_SixLaborsSampleAB.woff-50-ABAB-(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f05a32ebdbdca54454ea2624d085cfd4965cf676bbad36f9be9ad199a3b7faa8 -size 604 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid900x150_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(0,0).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid900x150_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(0,0).png deleted file mode 100644 index e243c035f..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_Solid900x150_(255,255,255,255)_OpenSans-Regular.ttf-50-Sphi-(0,0).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:803037eed7e876797e3920b3b8b1c7874a90affed7360c7911be63405ab37a08 -size 10630 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-Sphi-(550,550).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-Sphi-(550,550).png deleted file mode 100644 index cd00ebe1a..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(45)-Sphi-(550,550).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:03f8b2b0340e28882217f09502961d26422905144bd55681627a82b02fcc3f42 -size 15823 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(45)-ABAB-(100,100).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(45)-ABAB-(100,100).png deleted file mode 100644 index ca42e83e7..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(45)-ABAB-(100,100).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4fd14861fa01d9dc06a0bd2872ff24547cb366784c2d6af35b687e21783ca5f0 -size 1083 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(45)-Sphi-(200,200).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(45)-Sphi-(200,200).png deleted file mode 100644 index 46ca78bc8..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithRotationApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(45)-Sphi-(200,200).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ae72474cd3fa4ca95f93a82fd7b7f544c06f7307faf293151e1d2ce0433fbc1 -size 5234 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(0,10)-Sphi-(550,550).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(0,10)-Sphi-(550,550).png deleted file mode 100644 index 6d0f59b16..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid1100x1100_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(0,10)-Sphi-(550,550).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd361bad89a3ad48ca0e54b7493f51cfde973f19c44ff3e8af3179bdfb30a9c2 -size 14692 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(10,0)-ABAB-(100,100).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(10,0)-ABAB-(100,100).png deleted file mode 100644 index f6c0883a1..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid200x200_(255,255,255,255)_F(SixLaborsSampleAB.woff)-S(50)-A(10,0)-ABAB-(100,100).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a3ed8e0c4188a81e77da1d5d769865d8de26076f6a60a358bea96299c00718a -size 1000 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(0,-10)-Sphi-(200,200).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(0,-10)-Sphi-(200,200).png deleted file mode 100644 index 59db80f48..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid400x400_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(20)-A(0,-10)-Sphi-(200,200).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6223e0db0e24f739b50afb77cf3cb18c6043fe95cc643a201fd70df1c5ef2da4 -size 5021 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(-12,0)-i-(25,25).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(-12,0)-i-(25,25).png deleted file mode 100644 index 44f4e51a9..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/FontShapesAreRenderedCorrectly_WithSkewApplied_Solid50x50_(255,255,255,255)_F(OpenSans-Regular.ttf)-S(50)-A(-12,0)-i-(25,25).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab5e2ebd26e2c828c0ecde3e6a711adc1bf595ed08581ba223db6a36433b8dee -size 295 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/PathAndTextDrawingMatch_Rgba32_Solid1000x1000_(255,255,255,255).png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/PathAndTextDrawingMatch_Rgba32_Solid1000x1000_(255,255,255,255).png deleted file mode 100644 index 0ee6e7102..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/PathAndTextDrawingMatch_Rgba32_Solid1000x1000_(255,255,255,255).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:617e6041a8f312f0890d0b287f1a7191407a83b7aa3f47bdf214ae702ef32ee1 -size 36248 diff --git a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/TextPositioningIsRobust_OpenSans-Regular.ttf.png b/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/TextPositioningIsRobust_OpenSans-Regular.ttf.png deleted file mode 100644 index 6d70f32c2..000000000 --- a/tests/Images/ReferenceOutput/Drawing/Text/DrawTextOnImageTests/TextPositioningIsRobust_OpenSans-Regular.ttf.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f839ffbac1b001539912b2759206d2b3de2235f059e487505e5fb6226396c531 -size 184457 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/CanApplyPerspectiveTransform_StarWarsCrawl_StarWarsCrawl_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/CanApplyPerspectiveTransform_StarWarsCrawl_StarWarsCrawl_Default.png new file mode 100644 index 000000000..e7129410d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/CanApplyPerspectiveTransform_StarWarsCrawl_StarWarsCrawl_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8967c06efd7f3d7cf85917969b129ee33865432793e6206711b3486cf600536e +size 32295 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/CanApplyPerspectiveTransform_StarWarsCrawl_StarWarsCrawl_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/CanApplyPerspectiveTransform_StarWarsCrawl_StarWarsCrawl_WebGPU_NativeSurface.png new file mode 100644 index 000000000..e56276354 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/CanApplyPerspectiveTransform_StarWarsCrawl_StarWarsCrawl_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07d7da5068913f18909d370641348922a1b94de0ced86fadf9beeccba8576e13 +size 32134 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Butt_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Butt_Default.png new file mode 100644 index 000000000..b1654d308 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Butt_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92cb29d99646955e70bcd07b0061471c3168dd471f6d0280bebfbb48f1b5dedc +size 957 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Butt_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Butt_WebGPU_NativeSurface.png new file mode 100644 index 000000000..b1654d308 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Butt_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92cb29d99646955e70bcd07b0061471c3168dd471f6d0280bebfbb48f1b5dedc +size 957 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Round_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Round_Default.png new file mode 100644 index 000000000..7aeb788d6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Round_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45a3ccced32ad9e31239a882386973d4123d69934627b47096ea1b9450c63775 +size 1083 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Round_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Round_WebGPU_NativeSurface.png new file mode 100644 index 000000000..09b1672a6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Round_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:420a03c8f1f59cba44111ece194b71958d2d519228e1c26427e02cca87173fd0 +size 1083 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Square_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Square_Default.png new file mode 100644 index 000000000..c2056aacb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Square_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6320f82d43ac4c8474cba384cae43217c2abaec77762d754d4c0d13465b6743b +size 869 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Square_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Square_WebGPU_NativeSurface.png new file mode 100644 index 000000000..c2056aacb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineCap_MatchesDefaultOutput_DrawPath_Stroke_LineCap_Square_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6320f82d43ac4c8474cba384cae43217c2abaec77762d754d4c0d13465b6743b +size 869 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Bevel_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Bevel_Default.png new file mode 100644 index 000000000..b4c6a8bb5 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Bevel_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ad6725d8ebb7f255855e600a9036d4649a844e7bc6ca6e0993c4afac75fe056 +size 2932 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Bevel_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Bevel_WebGPU_NativeSurface.png new file mode 100644 index 000000000..a37448cd3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Bevel_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7aa551c29f2eef2434f878c1241e374b74cda4347b41c8899e597e12a4a78ec +size 3323 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRevert_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRevert_Default.png new file mode 100644 index 000000000..332e8d803 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRevert_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05eccdd3499749dbf54338e0f72890484a9f4412ec98f5eebed1073e951f57f1 +size 3135 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRevert_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRevert_WebGPU_NativeSurface.png new file mode 100644 index 000000000..a4b78daf4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRevert_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79db4f813ef3bc5364cadc2953e13a2541c2e136945eae5d402611ac04700c2e +size 3499 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRound_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRound_Default.png new file mode 100644 index 000000000..332e8d803 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRound_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05eccdd3499749dbf54338e0f72890484a9f4412ec98f5eebed1073e951f57f1 +size 3135 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRound_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRound_WebGPU_NativeSurface.png new file mode 100644 index 000000000..a4b78daf4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_MiterRound_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79db4f813ef3bc5364cadc2953e13a2541c2e136945eae5d402611ac04700c2e +size 3499 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Miter_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Miter_Default.png new file mode 100644 index 000000000..332e8d803 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Miter_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05eccdd3499749dbf54338e0f72890484a9f4412ec98f5eebed1073e951f57f1 +size 3135 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Miter_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Miter_WebGPU_NativeSurface.png new file mode 100644 index 000000000..a4b78daf4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Miter_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79db4f813ef3bc5364cadc2953e13a2541c2e136945eae5d402611ac04700c2e +size 3499 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Round_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Round_Default.png new file mode 100644 index 000000000..74f9d6ab1 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Round_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72cb6df264e1b343af92dfecb0585ae6b4b0613973b8b4e6e03b0fb69457bd7a +size 3039 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Round_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Round_WebGPU_NativeSurface.png new file mode 100644 index 000000000..e656f164b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_LineJoin_MatchesDefaultOutput_DrawPath_Stroke_LineJoin_Round_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a28a8b235e21f19b9b2dd35f666fec1f21c31da9c5b84732ad1c9243fc78c5cc +size 3419 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_MatchesDefaultOutput_DrawPath_Stroke_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_MatchesDefaultOutput_DrawPath_Stroke_Default.png new file mode 100644 index 000000000..a04491b19 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_MatchesDefaultOutput_DrawPath_Stroke_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74613b0a8e9b7ca778876526d878214eba77dfcb61dab80f84dbf171bd9ed5b8 +size 1963 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_MatchesDefaultOutput_DrawPath_Stroke_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_MatchesDefaultOutput_DrawPath_Stroke_WebGPU_NativeSurface.png new file mode 100644 index 000000000..f0500c2d7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawPath_Stroke_MatchesDefaultOutput_DrawPath_Stroke_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac7a7ae06eb322c60aa1faad8c880df71e943ae37948ccf50dd2394a044012f3 +size 2264 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_AfterClear_UsesBlendFastPath_RepeatedGlyphs_AfterClear_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_AfterClear_UsesBlendFastPath_RepeatedGlyphs_AfterClear_Default.png new file mode 100644 index 000000000..26c5cd268 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_AfterClear_UsesBlendFastPath_RepeatedGlyphs_AfterClear_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f28dcd171fbd0d90f3f36d155f086d7d054d21a86cc637353ede310920238baa +size 4678 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_AfterClear_UsesBlendFastPath_RepeatedGlyphs_AfterClear_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_AfterClear_UsesBlendFastPath_RepeatedGlyphs_AfterClear_WebGPU_NativeSurface.png new file mode 100644 index 000000000..0fe683c1d --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_AfterClear_UsesBlendFastPath_RepeatedGlyphs_AfterClear_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e06b27fd2bb367ac3ed7c57777a5073ccacc99ba99bbbdfef238f905a7c58cee +size 4693 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_UsesCoverageCache_RepeatedGlyphs_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_UsesCoverageCache_RepeatedGlyphs_Default.png new file mode 100644 index 000000000..162e2d0a6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_UsesCoverageCache_RepeatedGlyphs_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca43950637e94a3cc27ece19a223325ae3d4b30e25e79ec3ebcf00cc91c8cc9a +size 4883 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_UsesCoverageCache_RepeatedGlyphs_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_UsesCoverageCache_RepeatedGlyphs_WebGPU_NativeSurface.png new file mode 100644 index 000000000..7024ebfef --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithRepeatedGlyphs_UsesCoverageCache_RepeatedGlyphs_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c387a6f663c4badd82784e90e020a9c5aa5cc8a1486cd7570c6a41dee0e88ab8 +size 4885 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithWebGPUCoverageBackend_RendersAndReleasesPreparedCoverage_DrawText_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithWebGPUCoverageBackend_RendersAndReleasesPreparedCoverage_DrawText_Default.png new file mode 100644 index 000000000..865cef7dc --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithWebGPUCoverageBackend_RendersAndReleasesPreparedCoverage_DrawText_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:280935e7896f0da062ab8ec4c2455f09f95d88728b4cd28a697a942841002d7c +size 36452 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithWebGPUCoverageBackend_RendersAndReleasesPreparedCoverage_DrawText_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithWebGPUCoverageBackend_RendersAndReleasesPreparedCoverage_DrawText_WebGPU_NativeSurface.png new file mode 100644 index 000000000..9200bf044 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/DrawText_WithWebGPUCoverageBackend_RendersAndReleasesPreparedCoverage_DrawText_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2e29507a7e569a2f0a887f196719b5943495184d9b33c8319760bcc78a9a0a7 +size 36329 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_AliasedWithThreshold_MatchesDefaultOutput_FillPath_AliasedThreshold_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_AliasedWithThreshold_MatchesDefaultOutput_FillPath_AliasedThreshold_Default.png new file mode 100644 index 000000000..71fe4495e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_AliasedWithThreshold_MatchesDefaultOutput_FillPath_AliasedThreshold_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11a4ff84a5a8a142f076a26b9e0066410ac96aba34bc1916abbdb487ad9eb989 +size 523 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_AliasedWithThreshold_MatchesDefaultOutput_FillPath_AliasedThreshold_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_AliasedWithThreshold_MatchesDefaultOutput_FillPath_AliasedThreshold_WebGPU_NativeSurface.png new file mode 100644 index 000000000..71fe4495e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_AliasedWithThreshold_MatchesDefaultOutput_FillPath_AliasedThreshold_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11a4ff84a5a8a142f076a26b9e0066410ac96aba34bc1916abbdb487ad9eb989 +size 523 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_EvenOddRule_MatchesDefaultOutput_FillPath_EvenOdd_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_EvenOddRule_MatchesDefaultOutput_FillPath_EvenOdd_Default.png new file mode 100644 index 000000000..09adf1c65 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_EvenOddRule_MatchesDefaultOutput_FillPath_EvenOdd_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04b44c8cc975defeb5235bd8c84eb03726a156a2c744c9e0b6289a705179fad1 +size 138 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_EvenOddRule_MatchesDefaultOutput_FillPath_EvenOdd_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_EvenOddRule_MatchesDefaultOutput_FillPath_EvenOdd_WebGPU_NativeSurface.png new file mode 100644 index 000000000..09adf1c65 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_EvenOddRule_MatchesDefaultOutput_FillPath_EvenOdd_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04b44c8cc975defeb5235bd8c84eb03726a156a2c744c9e0b6289a705179fad1 +size 138 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_LargeTileCount_MatchesDefaultOutput_FillPath_LargeTileCount_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_LargeTileCount_MatchesDefaultOutput_FillPath_LargeTileCount_Default.png new file mode 100644 index 000000000..b37aefc21 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_LargeTileCount_MatchesDefaultOutput_FillPath_LargeTileCount_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7921accb83e6cc2c3b537a0c354a403d15cfcad95aa1e352d5811a2082a69fc3 +size 6093 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_LargeTileCount_MatchesDefaultOutput_FillPath_LargeTileCount_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_LargeTileCount_MatchesDefaultOutput_FillPath_LargeTileCount_WebGPU_NativeSurface.png new file mode 100644 index 000000000..b37aefc21 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_LargeTileCount_MatchesDefaultOutput_FillPath_LargeTileCount_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7921accb83e6cc2c3b537a0c354a403d15cfcad95aa1e352d5811a2082a69fc3 +size 6093 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_MultipleSeparatePaths_MatchesDefaultOutput_FillPath_MultipleSeparate_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_MultipleSeparatePaths_MatchesDefaultOutput_FillPath_MultipleSeparate_Default.png new file mode 100644 index 000000000..3b25b6ccb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_MultipleSeparatePaths_MatchesDefaultOutput_FillPath_MultipleSeparate_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9aa3913abd295949b3d22e8bd2031a406a11dcc9a302f3856eaa9e37cbe112d +size 336 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_MultipleSeparatePaths_MatchesDefaultOutput_FillPath_MultipleSeparate_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_MultipleSeparatePaths_MatchesDefaultOutput_FillPath_MultipleSeparate_WebGPU_NativeSurface.png new file mode 100644 index 000000000..3b25b6ccb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_MultipleSeparatePaths_MatchesDefaultOutput_FillPath_MultipleSeparate_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9aa3913abd295949b3d22e8bd2031a406a11dcc9a302f3856eaa9e37cbe112d +size 336 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_MatchesDefaultOutput_FillPath_EllipticGradient_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_MatchesDefaultOutput_FillPath_EllipticGradient_Default.png new file mode 100644 index 000000000..0312b3f84 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_MatchesDefaultOutput_FillPath_EllipticGradient_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37df4c3c1e2174e4ce5ccc1cf56463e0232e8147106e996c432233eb56f31b48 +size 4349 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_MatchesDefaultOutput_FillPath_EllipticGradient_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_MatchesDefaultOutput_FillPath_EllipticGradient_WebGPU_NativeSurface.png new file mode 100644 index 000000000..0312b3f84 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_MatchesDefaultOutput_FillPath_EllipticGradient_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37df4c3c1e2174e4ce5ccc1cf56463e0232e8147106e996c432233eb56f31b48 +size 4349 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_Reflect_MatchesDefaultOutput_FillPath_EllipticGradient_Reflect_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_Reflect_MatchesDefaultOutput_FillPath_EllipticGradient_Reflect_Default.png new file mode 100644 index 000000000..068a89979 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_Reflect_MatchesDefaultOutput_FillPath_EllipticGradient_Reflect_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13405f754723b9cff04307e94079a2826a25c20e16957bb5abea556aceea4399 +size 38897 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_Reflect_MatchesDefaultOutput_FillPath_EllipticGradient_Reflect_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_Reflect_MatchesDefaultOutput_FillPath_EllipticGradient_Reflect_WebGPU_NativeSurface.png new file mode 100644 index 000000000..8bf73279f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithEllipticGradientBrush_Reflect_MatchesDefaultOutput_FillPath_EllipticGradient_Reflect_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca53253afc2f40e32a424bf6deb0791f3d688baacaf8c86662cea97dd5fd0dea +size 38880 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Add_Src_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Add_Src_Default.png new file mode 100644 index 000000000..1a905ff83 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Add_Src_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13062ce218d198269d6b2f130182c0ea30bf12a3460e72d6dcb57a2975bdf719 +size 566 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Add_Src_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Add_Src_WebGPU_NativeSurface.png new file mode 100644 index 000000000..1a905ff83 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Add_Src_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13062ce218d198269d6b2f130182c0ea30bf12a3460e72d6dcb57a2975bdf719 +size 566 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Darken_DestAtop_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Darken_DestAtop_Default.png new file mode 100644 index 000000000..1b1ed3e3b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Darken_DestAtop_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70c77c3bad7249bdd0231f273e06c2ddfb46683aedc59644f1fd07baff3ecc9c +size 826 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Darken_DestAtop_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Darken_DestAtop_WebGPU_NativeSurface.png new file mode 100644 index 000000000..1b1ed3e3b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Darken_DestAtop_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70c77c3bad7249bdd0231f273e06c2ddfb46683aedc59644f1fd07baff3ecc9c +size 826 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_HardLight_Xor_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_HardLight_Xor_Default.png new file mode 100644 index 000000000..0d51f838c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_HardLight_Xor_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0b811939ce1323656bb91c88841c9c33419ecbe511cc3ff623f5a3e117035bd +size 804 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_HardLight_Xor_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_HardLight_Xor_WebGPU_NativeSurface.png new file mode 100644 index 000000000..0d51f838c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_HardLight_Xor_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0b811939ce1323656bb91c88841c9c33419ecbe511cc3ff623f5a3e117035bd +size 804 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Lighten_DestIn_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Lighten_DestIn_Default.png new file mode 100644 index 000000000..00a793ec2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Lighten_DestIn_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e877874f1c5f36f423c177a9b891b52f748426fbd76c38744f28745ee8fb1cf9 +size 798 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Lighten_DestIn_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Lighten_DestIn_WebGPU_NativeSurface.png new file mode 100644 index 000000000..00a793ec2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Lighten_DestIn_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e877874f1c5f36f423c177a9b891b52f748426fbd76c38744f28745ee8fb1cf9 +size 798 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Multiply_SrcAtop_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Multiply_SrcAtop_Default.png new file mode 100644 index 000000000..443c5e78e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Multiply_SrcAtop_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c78b60cfef6fca9cf9c1f1bd1b238c659a307a33693d12ccfc86a9a520b65de +size 781 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Multiply_SrcAtop_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Multiply_SrcAtop_WebGPU_NativeSurface.png new file mode 100644 index 000000000..443c5e78e --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Multiply_SrcAtop_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c78b60cfef6fca9cf9c1f1bd1b238c659a307a33693d12ccfc86a9a520b65de +size 781 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_Clear_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_Clear_Default.png new file mode 100644 index 000000000..d835b86af --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_Clear_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38f8361e3cdac288d1276a33393c8dbbbb1bbe4e239dd99c36fba7b91ff0ff46 +size 446 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_Clear_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_Clear_WebGPU_NativeSurface.png new file mode 100644 index 000000000..d835b86af --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_Clear_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38f8361e3cdac288d1276a33393c8dbbbb1bbe4e239dd99c36fba7b91ff0ff46 +size 446 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_SrcOver_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_SrcOver_Default.png new file mode 100644 index 000000000..0627f844b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_SrcOver_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06502181f6cef0bb53cd4c142009a7da9093533f2cb6188f78e75036db4fbe7f +size 828 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_SrcOver_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_SrcOver_WebGPU_NativeSurface.png new file mode 100644 index 000000000..0627f844b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Normal_SrcOver_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06502181f6cef0bb53cd4c142009a7da9093533f2cb6188f78e75036db4fbe7f +size 828 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Overlay_SrcIn_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Overlay_SrcIn_Default.png new file mode 100644 index 000000000..71d06c28f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Overlay_SrcIn_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:211e9f0118bb44a4b539400d74aed635cf951a5834e330b2d74416d5e9b6dd0a +size 533 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Overlay_SrcIn_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Overlay_SrcIn_WebGPU_NativeSurface.png new file mode 100644 index 000000000..71d06c28f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Overlay_SrcIn_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:211e9f0118bb44a4b539400d74aed635cf951a5834e330b2d74416d5e9b6dd0a +size 533 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Screen_DestOver_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Screen_DestOver_Default.png new file mode 100644 index 000000000..d8b6ebb18 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Screen_DestOver_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf632398801d9696749701016ab1c35341c62ca87f8f9ffa1b634a03e518a6c9 +size 834 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Screen_DestOver_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Screen_DestOver_WebGPU_NativeSurface.png new file mode 100644 index 000000000..d8b6ebb18 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Screen_DestOver_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf632398801d9696749701016ab1c35341c62ca87f8f9ffa1b634a03e518a6c9 +size 834 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Subtract_DestOut_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Subtract_DestOut_Default.png new file mode 100644 index 000000000..212ff2e1a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Subtract_DestOut_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:448effa8e8cac551ffb3f849109f07291e575cbcd0c6e8bb4cc7b8c514772312 +size 793 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Subtract_DestOut_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Subtract_DestOut_WebGPU_NativeSurface.png new file mode 100644 index 000000000..212ff2e1a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_ImageBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_ImageBrush_Subtract_DestOut_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:448effa8e8cac551ffb3f849109f07291e575cbcd0c6e8bb4cc7b8c514772312 +size 793 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Add_Src_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Add_Src_Default.png new file mode 100644 index 000000000..bcc59e5ae --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Add_Src_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4215d621ff15138795a72651e8aba14fca5aea4356b1d3a1687d78e2306e71f8 +size 472 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Add_Src_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Add_Src_WebGPU_NativeSurface.png new file mode 100644 index 000000000..bcc59e5ae --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Add_Src_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4215d621ff15138795a72651e8aba14fca5aea4356b1d3a1687d78e2306e71f8 +size 472 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Darken_DestAtop_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Darken_DestAtop_Default.png new file mode 100644 index 000000000..ff3590331 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Darken_DestAtop_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b88bdda75c9f2addee9d898b9d9dcbfa45f247de2d9f4f771b3d31051fc8dd88 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Darken_DestAtop_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Darken_DestAtop_WebGPU_NativeSurface.png new file mode 100644 index 000000000..ff3590331 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Darken_DestAtop_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b88bdda75c9f2addee9d898b9d9dcbfa45f247de2d9f4f771b3d31051fc8dd88 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_HardLight_Xor_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_HardLight_Xor_Default.png new file mode 100644 index 000000000..c561128ef --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_HardLight_Xor_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b228b04cbfabb613782ce0569aecae88ab8de33ce5f853bb10016b266f8cfa30 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_HardLight_Xor_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_HardLight_Xor_WebGPU_NativeSurface.png new file mode 100644 index 000000000..c561128ef --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_HardLight_Xor_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b228b04cbfabb613782ce0569aecae88ab8de33ce5f853bb10016b266f8cfa30 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Lighten_DestIn_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Lighten_DestIn_Default.png new file mode 100644 index 000000000..43394d294 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Lighten_DestIn_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d274697d9d07a0f27e610e796452aa09db103a96473e7bac8decd0c656ee0d5 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Lighten_DestIn_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Lighten_DestIn_WebGPU_NativeSurface.png new file mode 100644 index 000000000..43394d294 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Lighten_DestIn_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d274697d9d07a0f27e610e796452aa09db103a96473e7bac8decd0c656ee0d5 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Multiply_SrcAtop_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Multiply_SrcAtop_Default.png new file mode 100644 index 000000000..31b07cb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Multiply_SrcAtop_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:630a5530b5484317a46507404825943321840e7803172a0895cc2c10d40a4338 +size 444 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Multiply_SrcAtop_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Multiply_SrcAtop_WebGPU_NativeSurface.png new file mode 100644 index 000000000..31b07cb56 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Multiply_SrcAtop_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:630a5530b5484317a46507404825943321840e7803172a0895cc2c10d40a4338 +size 444 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_Clear_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_Clear_Default.png new file mode 100644 index 000000000..d835b86af --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_Clear_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38f8361e3cdac288d1276a33393c8dbbbb1bbe4e239dd99c36fba7b91ff0ff46 +size 446 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_Clear_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_Clear_WebGPU_NativeSurface.png new file mode 100644 index 000000000..d835b86af --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_Clear_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38f8361e3cdac288d1276a33393c8dbbbb1bbe4e239dd99c36fba7b91ff0ff46 +size 446 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_SrcOver_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_SrcOver_Default.png new file mode 100644 index 000000000..1ad01578b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_SrcOver_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34cfa0616b966a9f675fa61c2cd9ff5b9637e452e2c6ff59f36f790314213a24 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_SrcOver_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_SrcOver_WebGPU_NativeSurface.png new file mode 100644 index 000000000..1ad01578b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Normal_SrcOver_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34cfa0616b966a9f675fa61c2cd9ff5b9637e452e2c6ff59f36f790314213a24 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Overlay_SrcIn_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Overlay_SrcIn_Default.png new file mode 100644 index 000000000..9f6074d7f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Overlay_SrcIn_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1433e8e3d4c0cf4f1a67080b5ceef482980177b4a6828048d05dea98e682697b +size 474 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Overlay_SrcIn_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Overlay_SrcIn_WebGPU_NativeSurface.png new file mode 100644 index 000000000..9f6074d7f --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Overlay_SrcIn_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1433e8e3d4c0cf4f1a67080b5ceef482980177b4a6828048d05dea98e682697b +size 474 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Screen_DestOver_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Screen_DestOver_Default.png new file mode 100644 index 000000000..c76cbf48c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Screen_DestOver_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90784f2523c8e7d6680cc17e618893ebf040033642a3cbaad73918c2f5d6b2f8 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Screen_DestOver_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Screen_DestOver_WebGPU_NativeSurface.png new file mode 100644 index 000000000..c76cbf48c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Screen_DestOver_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90784f2523c8e7d6680cc17e618893ebf040033642a3cbaad73918c2f5d6b2f8 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Subtract_DestOut_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Subtract_DestOut_Default.png new file mode 100644 index 000000000..b9ac5f192 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Subtract_DestOut_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6392bded60931b04bd5044a2e789405506bb8c98f4ac271e04af7698696c929 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Subtract_DestOut_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Subtract_DestOut_WebGPU_NativeSurface.png new file mode 100644 index 000000000..b9ac5f192 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithGraphicsOptionsModes_SolidBrush_MatchesDefaultOutput_FillPath_GraphicsOptions_SolidBrush_Subtract_DestOut_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6392bded60931b04bd5044a2e789405506bb8c98f4ac271e04af7698696c929 +size 471 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithImageBrush_MatchesDefaultOutput_FillPath_ImageBrush_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithImageBrush_MatchesDefaultOutput_FillPath_ImageBrush_Default.png new file mode 100644 index 000000000..55694d401 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithImageBrush_MatchesDefaultOutput_FillPath_ImageBrush_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74b4e0b213dd604413745b05195fb9bbf5eacac1883ade35b73f4985a800b69b +size 363 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithImageBrush_MatchesDefaultOutput_FillPath_ImageBrush_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithImageBrush_MatchesDefaultOutput_FillPath_ImageBrush_WebGPU_NativeSurface.png new file mode 100644 index 000000000..fdb8fff15 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithImageBrush_MatchesDefaultOutput_FillPath_ImageBrush_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d065e13c7ffde92b22a15a626842a92f39b65abcbf3a4c3561cd7341801f906c +size 363 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_MatchesDefaultOutput_FillPath_LinearGradient_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_MatchesDefaultOutput_FillPath_LinearGradient_Default.png new file mode 100644 index 000000000..f3acaaca4 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_MatchesDefaultOutput_FillPath_LinearGradient_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c52083eca151bfe6a69e2900d0bd19535e9e6d57dda489330d67c18274d313fc +size 5449 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_MatchesDefaultOutput_FillPath_LinearGradient_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_MatchesDefaultOutput_FillPath_LinearGradient_WebGPU_NativeSurface.png new file mode 100644 index 000000000..50d1ae9ce --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_MatchesDefaultOutput_FillPath_LinearGradient_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:391ac2e3a2f949744cbafd394b7fac092ca24a82dbc80868496089a77348c4a2 +size 5455 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_Repeat_MatchesDefaultOutput_FillPath_LinearGradient_Repeat_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_Repeat_MatchesDefaultOutput_FillPath_LinearGradient_Repeat_Default.png new file mode 100644 index 000000000..4c8c85716 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_Repeat_MatchesDefaultOutput_FillPath_LinearGradient_Repeat_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6831aad3db662c097dd6bc163eeeb80f90e802176dcbf7f29aa3696efae42d40 +size 1643 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_Repeat_MatchesDefaultOutput_FillPath_LinearGradient_Repeat_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_Repeat_MatchesDefaultOutput_FillPath_LinearGradient_Repeat_WebGPU_NativeSurface.png new file mode 100644 index 000000000..b5915fcba --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_Repeat_MatchesDefaultOutput_FillPath_LinearGradient_Repeat_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c112653620f65900de5796db2369b64165549940bbf3d8bbc978baa26fe3d09 +size 1643 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_ThreePoint_MatchesDefaultOutput_FillPath_LinearGradient_ThreePoint_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_ThreePoint_MatchesDefaultOutput_FillPath_LinearGradient_ThreePoint_Default.png new file mode 100644 index 000000000..ab47e6ae3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_ThreePoint_MatchesDefaultOutput_FillPath_LinearGradient_ThreePoint_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f670831068c21e3151e5ff2f1a985bf9ec26445b74b815de5aba50316995d20a +size 1370 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_ThreePoint_MatchesDefaultOutput_FillPath_LinearGradient_ThreePoint_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_ThreePoint_MatchesDefaultOutput_FillPath_LinearGradient_ThreePoint_WebGPU_NativeSurface.png new file mode 100644 index 000000000..fb153d398 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithLinearGradientBrush_ThreePoint_MatchesDefaultOutput_FillPath_LinearGradient_ThreePoint_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80d0f8a96b226d1a5034baa32ce5cbf14ef99a7a6ae7b75907c0271698a3a749 +size 1370 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithNonZeroNestedContours_MatchesDefaultOutput_FillPath_NonZeroNestedContours_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithNonZeroNestedContours_MatchesDefaultOutput_FillPath_NonZeroNestedContours_Default.png new file mode 100644 index 000000000..516e2f405 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithNonZeroNestedContours_MatchesDefaultOutput_FillPath_NonZeroNestedContours_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56e326664a279ba7e03c5439fb87fdea3065ce68b8407971c307df7af6e5c96c +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithNonZeroNestedContours_MatchesDefaultOutput_FillPath_NonZeroNestedContours_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithNonZeroNestedContours_MatchesDefaultOutput_FillPath_NonZeroNestedContours_WebGPU_NativeSurface.png new file mode 100644 index 000000000..516e2f405 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithNonZeroNestedContours_MatchesDefaultOutput_FillPath_NonZeroNestedContours_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56e326664a279ba7e03c5439fb87fdea3065ce68b8407971c307df7af6e5c96c +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_Diagonal_MatchesDefaultOutput_FillPath_PatternBrush_Diagonal_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_Diagonal_MatchesDefaultOutput_FillPath_PatternBrush_Diagonal_Default.png new file mode 100644 index 000000000..30c3e9349 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_Diagonal_MatchesDefaultOutput_FillPath_PatternBrush_Diagonal_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a0ded646cdc61bfff585b39f1beaa3b444e25c9866474fff335fc1b828526ac +size 2209 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_Diagonal_MatchesDefaultOutput_FillPath_PatternBrush_Diagonal_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_Diagonal_MatchesDefaultOutput_FillPath_PatternBrush_Diagonal_WebGPU_NativeSurface.png new file mode 100644 index 000000000..30c3e9349 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_Diagonal_MatchesDefaultOutput_FillPath_PatternBrush_Diagonal_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a0ded646cdc61bfff585b39f1beaa3b444e25c9866474fff335fc1b828526ac +size 2209 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_MatchesDefaultOutput_FillPath_PatternBrush_Horizontal_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_MatchesDefaultOutput_FillPath_PatternBrush_Horizontal_Default.png new file mode 100644 index 000000000..82c778d4a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_MatchesDefaultOutput_FillPath_PatternBrush_Horizontal_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd8a2d93a0306cf23bb1138b31d8fac263cf6bca49dea577d4caf2ff4bb52cbb +size 146 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_MatchesDefaultOutput_FillPath_PatternBrush_Horizontal_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_MatchesDefaultOutput_FillPath_PatternBrush_Horizontal_WebGPU_NativeSurface.png new file mode 100644 index 000000000..82c778d4a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithPatternBrush_MatchesDefaultOutput_FillPath_PatternBrush_Horizontal_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd8a2d93a0306cf23bb1138b31d8fac263cf6bca49dea577d4caf2ff4bb52cbb +size 146 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_SingleCircle_MatchesDefaultOutput_FillPath_RadialGradient_Single_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_SingleCircle_MatchesDefaultOutput_FillPath_RadialGradient_Single_Default.png new file mode 100644 index 000000000..23b687e57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_SingleCircle_MatchesDefaultOutput_FillPath_RadialGradient_Single_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af2824eed2bb429f76270556cbb939a7f32558fa5eb9d7ada891ab3c888f45b2 +size 10237 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_SingleCircle_MatchesDefaultOutput_FillPath_RadialGradient_Single_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_SingleCircle_MatchesDefaultOutput_FillPath_RadialGradient_Single_WebGPU_NativeSurface.png new file mode 100644 index 000000000..23b687e57 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_SingleCircle_MatchesDefaultOutput_FillPath_RadialGradient_Single_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af2824eed2bb429f76270556cbb939a7f32558fa5eb9d7ada891ab3c888f45b2 +size 10237 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_TwoCircle_MatchesDefaultOutput_FillPath_RadialGradient_TwoCircle_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_TwoCircle_MatchesDefaultOutput_FillPath_RadialGradient_TwoCircle_Default.png new file mode 100644 index 000000000..5916a439a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_TwoCircle_MatchesDefaultOutput_FillPath_RadialGradient_TwoCircle_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:219f9ce0010b283867e39ca551b6c39a69a8cc8d35790634d554d064c6dcaaca +size 1549 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_TwoCircle_MatchesDefaultOutput_FillPath_RadialGradient_TwoCircle_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_TwoCircle_MatchesDefaultOutput_FillPath_RadialGradient_TwoCircle_WebGPU_NativeSurface.png new file mode 100644 index 000000000..5916a439a --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRadialGradientBrush_TwoCircle_MatchesDefaultOutput_FillPath_RadialGradient_TwoCircle_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:219f9ce0010b283867e39ca551b6c39a69a8cc8d35790634d554d064c6dcaaca +size 1549 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRecolorBrush_MatchesDefaultOutput_FillPath_RecolorBrush_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRecolorBrush_MatchesDefaultOutput_FillPath_RecolorBrush_Default.png new file mode 100644 index 000000000..25a223a26 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRecolorBrush_MatchesDefaultOutput_FillPath_RecolorBrush_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd31a65567b5a4a498604fe0089b57d89b09640938731279c1cb14abb25cd830 +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRecolorBrush_MatchesDefaultOutput_FillPath_RecolorBrush_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRecolorBrush_MatchesDefaultOutput_FillPath_RecolorBrush_WebGPU_NativeSurface.png new file mode 100644 index 000000000..25a223a26 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithRecolorBrush_MatchesDefaultOutput_FillPath_RecolorBrush_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd31a65567b5a4a498604fe0089b57d89b09640938731279c1cb14abb25cd830 +size 127 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_MatchesDefaultOutput_FillPath_SweepGradient_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_MatchesDefaultOutput_FillPath_SweepGradient_Default.png new file mode 100644 index 000000000..ab8b52bcb --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_MatchesDefaultOutput_FillPath_SweepGradient_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edf24575d55a7d0474ce2e5520db0873469ce94b0c42a06ff9c5647582d41327 +size 15203 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_MatchesDefaultOutput_FillPath_SweepGradient_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_MatchesDefaultOutput_FillPath_SweepGradient_WebGPU_NativeSurface.png new file mode 100644 index 000000000..58d7de17b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_MatchesDefaultOutput_FillPath_SweepGradient_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f184d9b980d6df10fc55706bf2125c95f2663969cdfd586041f6f987ac90127 +size 15195 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_PartialArc_MatchesDefaultOutput_FillPath_SweepGradient_PartialArc_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_PartialArc_MatchesDefaultOutput_FillPath_SweepGradient_PartialArc_Default.png new file mode 100644 index 000000000..377cc81e6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_PartialArc_MatchesDefaultOutput_FillPath_SweepGradient_PartialArc_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d863787f2a3af2298ea34714f79c326e6444bf37c6961eed7d266d901c647b81 +size 15469 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_PartialArc_MatchesDefaultOutput_FillPath_SweepGradient_PartialArc_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_PartialArc_MatchesDefaultOutput_FillPath_SweepGradient_PartialArc_WebGPU_NativeSurface.png new file mode 100644 index 000000000..19121c6a7 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithSweepGradientBrush_PartialArc_MatchesDefaultOutput_FillPath_SweepGradient_PartialArc_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dfcbf2edcbd4dbf44243bd18ff3246440e8b0739f05d0d4b3c2c8117a28d4f0 +size 15482 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_MatchesDefaultOutput_FillPath_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_MatchesDefaultOutput_FillPath_Default.png new file mode 100644 index 000000000..883df5636 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_MatchesDefaultOutput_FillPath_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05e94f0d3fe81b28eb21796321e73dcc5ec8b94a965af761107d13b0bb2ff920 +size 714 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_MatchesDefaultOutput_FillPath_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_MatchesDefaultOutput_FillPath_WebGPU_NativeSurface.png new file mode 100644 index 000000000..883df5636 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_MatchesDefaultOutput_FillPath_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05e94f0d3fe81b28eb21796321e73dcc5ec8b94a965af761107d13b0bb2ff920 +size 714 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurfaceSubregion_MatchesDefaultOutput_FillPath_NativeSurfaceSubregionParity_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurfaceSubregion_MatchesDefaultOutput_FillPath_NativeSurfaceSubregionParity_Default.png new file mode 100644 index 000000000..55a946401 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurfaceSubregion_MatchesDefaultOutput_FillPath_NativeSurfaceSubregionParity_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f02ab5aef4c00977bc766e4a03b16efd08da105faf1a1495f33087bc882cd370 +size 491 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurfaceSubregion_MatchesDefaultOutput_FillPath_NativeSurfaceSubregionParity_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurfaceSubregion_MatchesDefaultOutput_FillPath_NativeSurfaceSubregionParity_WebGPU_NativeSurface.png new file mode 100644 index 000000000..55a946401 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurfaceSubregion_MatchesDefaultOutput_FillPath_NativeSurfaceSubregionParity_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f02ab5aef4c00977bc766e4a03b16efd08da105faf1a1495f33087bc882cd370 +size 491 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurface_MatchesDefaultOutput_FillPath_NativeSurfaceParity_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurface_MatchesDefaultOutput_FillPath_NativeSurfaceParity_Default.png new file mode 100644 index 000000000..883df5636 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurface_MatchesDefaultOutput_FillPath_NativeSurfaceParity_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05e94f0d3fe81b28eb21796321e73dcc5ec8b94a965af761107d13b0bb2ff920 +size 714 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurface_MatchesDefaultOutput_FillPath_NativeSurfaceParity_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurface_MatchesDefaultOutput_FillPath_NativeSurfaceParity_WebGPU_NativeSurface.png new file mode 100644 index 000000000..883df5636 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/FillPath_WithWebGPUCoverageBackend_NativeSurface_MatchesDefaultOutput_FillPath_NativeSurfaceParity_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05e94f0d3fe81b28eb21796321e73dcc5ec8b94a965af761107d13b0bb2ff920 +size 714 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/MultipleFlushes_OnSameBackend_ProduceCorrectResults_MultipleFlushes_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/MultipleFlushes_OnSameBackend_ProduceCorrectResults_MultipleFlushes_Default.png new file mode 100644 index 000000000..ced935c37 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/MultipleFlushes_OnSameBackend_ProduceCorrectResults_MultipleFlushes_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1baf8a477ff132f73d6ea5c116146729cc8d693d33422f0beb21432c34798ac5 +size 158 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/MultipleFlushes_OnSameBackend_ProduceCorrectResults_MultipleFlushes_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/MultipleFlushes_OnSameBackend_ProduceCorrectResults_MultipleFlushes_WebGPU_NativeSurface.png new file mode 100644 index 000000000..ced935c37 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/MultipleFlushes_OnSameBackend_ProduceCorrectResults_MultipleFlushes_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1baf8a477ff132f73d6ea5c116146729cc8d693d33422f0beb21432c34798ac5 +size 158 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/Process_WithWebGPUBackend_MatchesDefaultOutput_Process_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/Process_WithWebGPUBackend_MatchesDefaultOutput_Process_Default.png new file mode 100644 index 000000000..11493e17c --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/Process_WithWebGPUBackend_MatchesDefaultOutput_Process_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c2b561014aa035424a8745a1c20de3ce33efb5d11137b065a9ec43eed279dd8 +size 12884 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/Process_WithWebGPUBackend_MatchesDefaultOutput_Process_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/Process_WithWebGPUBackend_MatchesDefaultOutput_Process_WebGPU_NativeSurface.png new file mode 100644 index 000000000..4a8a5c6e5 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/Process_WithWebGPUBackend_MatchesDefaultOutput_Process_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3154bc52e2942fef0578166087ef4a7cd2b8d3bbff3a9eb2a75ca216143e860 +size 12952 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_FullOpacity_MatchesDefaultOutput_SaveLayer_FullOpacity_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_FullOpacity_MatchesDefaultOutput_SaveLayer_FullOpacity_Default.png new file mode 100644 index 000000000..27ae8e895 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_FullOpacity_MatchesDefaultOutput_SaveLayer_FullOpacity_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b60b22409f9dba870c52f04b39ad0051bb4397c00321fe935431558243d4812b +size 108 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_FullOpacity_MatchesDefaultOutput_SaveLayer_FullOpacity_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_FullOpacity_MatchesDefaultOutput_SaveLayer_FullOpacity_WebGPU_NativeSurface.png new file mode 100644 index 000000000..27ae8e895 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_FullOpacity_MatchesDefaultOutput_SaveLayer_FullOpacity_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b60b22409f9dba870c52f04b39ad0051bb4397c00321fe935431558243d4812b +size 108 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_HalfOpacity_MatchesDefaultOutput_SaveLayer_HalfOpacity_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_HalfOpacity_MatchesDefaultOutput_SaveLayer_HalfOpacity_Default.png new file mode 100644 index 000000000..f034e374b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_HalfOpacity_MatchesDefaultOutput_SaveLayer_HalfOpacity_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8ce1ab20efc7f8ab17b614e548a7169f1070a8b46b273e10ae0cd51b13f66b +size 108 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_HalfOpacity_MatchesDefaultOutput_SaveLayer_HalfOpacity_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_HalfOpacity_MatchesDefaultOutput_SaveLayer_HalfOpacity_WebGPU_NativeSurface.png new file mode 100644 index 000000000..f034e374b --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_HalfOpacity_MatchesDefaultOutput_SaveLayer_HalfOpacity_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8ce1ab20efc7f8ab17b614e548a7169f1070a8b46b273e10ae0cd51b13f66b +size 108 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_MixedSaveAndSaveLayer_MatchesDefaultOutput_SaveLayer_MixedSaveAndSaveLayer_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_MixedSaveAndSaveLayer_MatchesDefaultOutput_SaveLayer_MixedSaveAndSaveLayer_Default.png new file mode 100644 index 000000000..15e58f4c8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_MixedSaveAndSaveLayer_MatchesDefaultOutput_SaveLayer_MixedSaveAndSaveLayer_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a06c54b58d35e349cfd18aeaff9614ce5348e696ebd3da5f330b03a6d0a4ab4f +size 96 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_MixedSaveAndSaveLayer_MatchesDefaultOutput_SaveLayer_MixedSaveAndSaveLayer_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_MixedSaveAndSaveLayer_MatchesDefaultOutput_SaveLayer_MixedSaveAndSaveLayer_WebGPU_NativeSurface.png new file mode 100644 index 000000000..15e58f4c8 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_MixedSaveAndSaveLayer_MatchesDefaultOutput_SaveLayer_MixedSaveAndSaveLayer_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a06c54b58d35e349cfd18aeaff9614ce5348e696ebd3da5f330b03a6d0a4ab4f +size 96 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_NestedLayers_MatchesDefaultOutput_SaveLayer_NestedLayers_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_NestedLayers_MatchesDefaultOutput_SaveLayer_NestedLayers_Default.png new file mode 100644 index 000000000..fbecf9946 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_NestedLayers_MatchesDefaultOutput_SaveLayer_NestedLayers_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee1e8240f4bf43453704c3a7ac27b1f21174097048a94793c6bd900899175963 +size 107 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_NestedLayers_MatchesDefaultOutput_SaveLayer_NestedLayers_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_NestedLayers_MatchesDefaultOutput_SaveLayer_NestedLayers_WebGPU_NativeSurface.png new file mode 100644 index 000000000..fbecf9946 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_NestedLayers_MatchesDefaultOutput_SaveLayer_NestedLayers_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee1e8240f4bf43453704c3a7ac27b1f21174097048a94793c6bd900899175963 +size 107 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBlendMode_MatchesDefaultOutput_SaveLayer_WithBlendMode_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBlendMode_MatchesDefaultOutput_SaveLayer_WithBlendMode_Default.png new file mode 100644 index 000000000..c8c5208e6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBlendMode_MatchesDefaultOutput_SaveLayer_WithBlendMode_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:528d775147faaf132df52f78b80356f3df98ef519779ade404a2d6f819d265cc +size 131 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBlendMode_MatchesDefaultOutput_SaveLayer_WithBlendMode_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBlendMode_MatchesDefaultOutput_SaveLayer_WithBlendMode_WebGPU_NativeSurface.png new file mode 100644 index 000000000..c8c5208e6 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBlendMode_MatchesDefaultOutput_SaveLayer_WithBlendMode_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:528d775147faaf132df52f78b80356f3df98ef519779ade404a2d6f819d265cc +size 131 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBounds_MatchesDefaultOutput_SaveLayer_WithBounds_Default.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBounds_MatchesDefaultOutput_SaveLayer_WithBounds_Default.png new file mode 100644 index 000000000..b4f2bc648 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBounds_MatchesDefaultOutput_SaveLayer_WithBounds_Default.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f74fc36ceebd139d08347771b75dd525bc7bb50fa78ae66cfc0480d25bc647a2 +size 107 diff --git a/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBounds_MatchesDefaultOutput_SaveLayer_WithBounds_WebGPU_NativeSurface.png b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBounds_MatchesDefaultOutput_SaveLayer_WithBounds_WebGPU_NativeSurface.png new file mode 100644 index 000000000..b4f2bc648 --- /dev/null +++ b/tests/Images/ReferenceOutput/Drawing/WebGPUDrawingBackendTests/SaveLayer_WithBounds_MatchesDefaultOutput_SaveLayer_WithBounds_WebGPU_NativeSurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f74fc36ceebd139d08347771b75dd525bc7bb50fa78ae66cfc0480d25bc647a2 +size 107 diff --git a/tests/Images/ReferenceOutput/Drawing/optimize-all.cmd b/tests/Images/ReferenceOutput/Drawing/optimize-all.cmd deleted file mode 100644 index 98b5eb6f2..000000000 --- a/tests/Images/ReferenceOutput/Drawing/optimize-all.cmd +++ /dev/null @@ -1 +0,0 @@ -optipng.exe -o 7 ./**/*.png \ No newline at end of file diff --git a/tests/Images/ReferenceOutput/Drawing/optipng.exe b/tests/Images/ReferenceOutput/Drawing/optipng.exe deleted file mode 100644 index 49f9dee09..000000000 Binary files a/tests/Images/ReferenceOutput/Drawing/optipng.exe and /dev/null differ diff --git a/tests/Images/ReferenceOutput/Issue_134/LowFontSizeRenderOK_Rgba32_False.png b/tests/Images/ReferenceOutput/Issue_134/LowFontSizeRenderOK_Rgba32_False.png new file mode 100644 index 000000000..adef79dcd --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_134/LowFontSizeRenderOK_Rgba32_False.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67515a55bc0bed27054d344d430e0ea0ab0ff15a5038b6b33e8f6118f2d84d42 +size 176 diff --git a/tests/Images/ReferenceOutput/Issue_134/LowFontSizeRenderOK_Rgba32_True.png b/tests/Images/ReferenceOutput/Issue_134/LowFontSizeRenderOK_Rgba32_True.png new file mode 100644 index 000000000..c2628e2ad --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_134/LowFontSizeRenderOK_Rgba32_True.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19039af8089b9ba3544553e7bd1248c135a8d9c17bdfc46728093bff7eef4bbf +size 675 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png index 1770f1516..29685ad13 100644 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc86c51ad4946fb8a314c8d869a83cc2496d30468036729c3827c2c121cae69c -size 1068 +oid sha256:35060ebe93aba15a6a0982ded5a13e6d3b01bc3d22a6cf65498d775aff721c88 +size 262 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png deleted file mode 100644 index a60e07711..000000000 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:62f86685f6f2326e629b8b84340bb1b3cbcf6ffe75facff0931b613337772345 -size 3296 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png deleted file mode 100644 index 5bf6378e5..000000000 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:534d3fece38b94386b6ba20aa121addda1beea61d38cb34b9d2ae09b662fd38b -size 3585 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png deleted file mode 100644 index 61df79c32..000000000 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4fff4ab1fec04c432529fd67d9c50934ce083fa6e7c0c4432fd6520eac2e53ac -size 3625 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png deleted file mode 100644 index d5630da20..000000000 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:92a356847e8aa36b361a2208021a0c0e2a3287d615f0b948bdbcc0bc3b336bc4 -size 4300 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png index d93b91a30..29685ad13 100644 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84eff105c799ed23497870d1a13d2e69986cf7240da2d508794b2974bee1c5b6 -size 254 +oid sha256:35060ebe93aba15a6a0982ded5a13e6d3b01bc3d22a6cf65498d775aff721c88 +size 262 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png index f0cad6422..4bd17cf3b 100644 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3581673cd0c053326d7c6947d232f62a7c0c61f3b86aa881be40ae609278100c -size 1188 +oid sha256:3f5c71327671428fabd8570ac86a823fd723eca816a75106424e1e0b419b49c6 +size 1039 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png index 3e0bec9ce..8a1721063 100644 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ccc8f2a14e5a3c8f7aca9c9411dbff01d8e1a3c7dbd66d515881bd753ebf922 -size 1284 +oid sha256:d9e53b3058726c860b17d9672b43300d80bacee1dbc8f38bfc48ca20dd82e74b +size 1100 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png index 78e2037c1..959e8d24b 100644 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5befd1a942a8b50b40b1f4e2938699b84239d028bd83e31445271ec5f2043c64 -size 1238 +oid sha256:8cde7d8a2ecb705e5c727c950368c083ca375588a12e945054d2ece6f91d5e5a +size 1136 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png index a4f0a8bfa..1bef63da3 100644 --- a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e491edd79708d65a78d7eec5e32f4ec108d2b73fdc19ea614f09ea02f8e4183 -size 1373 +oid sha256:be1c4e5ad79a9b08a0eb9323406b46b14cffecc2dde635220fbaf8599bb1738c +size 1254 diff --git a/tests/Images/ReferenceOutput/Issue_330/OffsetTextOutlines_Rgba32_Solid2084x2084_(138,43,226,255).png b/tests/Images/ReferenceOutput/Issue_330/OffsetTextOutlines_Rgba32_Solid2084x2084_(138,43,226,255).png index 473293933..095abbcf4 100644 --- a/tests/Images/ReferenceOutput/Issue_330/OffsetTextOutlines_Rgba32_Solid2084x2084_(138,43,226,255).png +++ b/tests/Images/ReferenceOutput/Issue_330/OffsetTextOutlines_Rgba32_Solid2084x2084_(138,43,226,255).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69c50b96bfc9c30b3d53ca17503ed5072f0e83a0541cfe0ef5570f3549d5b1e4 -size 116690 +oid sha256:38dde98c9995bc7972e2c02c93cb134ef7cb0da093fdd2e24b47b9085dcd1932 +size 116223 diff --git a/tests/Images/ReferenceOutput/Issue_344/CanDrawWhereSegmentsOverlap_PathBuilder_Rgba32_Solid100x100_(0,0,0,255).png b/tests/Images/ReferenceOutput/Issue_344/CanDrawWhereSegmentsOverlap_PathBuilder_Rgba32_Solid100x100_(0,0,0,255).png new file mode 100644 index 000000000..015bd5aa2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_344/CanDrawWhereSegmentsOverlap_PathBuilder_Rgba32_Solid100x100_(0,0,0,255).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6628022994715b9a00cc6410236b8c2b26b2d1bbf50e1b9163a5b87a8a7d2bd0 +size 111 diff --git a/tests/Images/ReferenceOutput/Issue_344/CanDrawWhereSegmentsOverlap_Rgba32_Solid100x100_(0,0,0,255).png b/tests/Images/ReferenceOutput/Issue_344/CanDrawWhereSegmentsOverlap_Rgba32_Solid100x100_(0,0,0,255).png new file mode 100644 index 000000000..015bd5aa2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_344/CanDrawWhereSegmentsOverlap_Rgba32_Solid100x100_(0,0,0,255).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6628022994715b9a00cc6410236b8c2b26b2d1bbf50e1b9163a5b87a8a7d2bd0 +size 111 diff --git a/tests/Images/ReferenceOutput/Issue_367/BrushAndTextAlign_Rgba32.png b/tests/Images/ReferenceOutput/Issue_367/BrushAndTextAlign_Rgba32.png new file mode 100644 index 000000000..2f25697cc --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_367/BrushAndTextAlign_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c54534ff4610093a76df8fd82f23618f080b0aa0718a7b06a18c27861dcc73d +size 8920 diff --git a/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_ColrV1-draw.png b/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_ColrV1-draw.png index 6dd59fe24..fccfaa9f0 100644 --- a/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_ColrV1-draw.png +++ b/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_ColrV1-draw.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:742e4bd37428a4402b097eb2e33c0cc2611cb17040a34ee1457508b630705f62 -size 31937 +oid sha256:1b359e29a789870afec6bb043472faae4d9ff9f5f4c994fc55436ffb7d80c1e9 +size 31935 diff --git a/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_ColrV1-fill.png b/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_ColrV1-fill.png index 462ffcfc5..08cc87986 100644 --- a/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_ColrV1-fill.png +++ b/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_ColrV1-fill.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:919a6c8b5be40aa3894050f033d487f90d6bd2621cfb2f337874bd20904d9603 -size 10646 +oid sha256:cc680ab6ee1d5d28f6b35e959402d0ee28f314a9cfc5df39cb04f234e9b51b8f +size 11082 diff --git a/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_Svg-draw.png b/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_Svg-draw.png index 8cc405e45..91c33671a 100644 --- a/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_Svg-draw.png +++ b/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_Svg-draw.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48b6a904ad0557908dd053ff357b8c10d4e279eeaf6dd9d0df40aee653ecca72 -size 31954 +oid sha256:ccfe86f8c60ff7fc1839959712b039eb9c32cc4cd1c3c182fb55ae83a2f3f1f3 +size 31952 diff --git a/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_Svg-fill.png b/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_Svg-fill.png index f3deebc62..829180a52 100644 --- a/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_Svg-fill.png +++ b/tests/Images/ReferenceOutput/Issue_462/CanDrawEmojiFont_Rgba32_Solid492x360_(255,255,255,255)_Svg-fill.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a0df948f516294d3499aaab857729635b160f6ad15adc93c81fbade0fecfce7 -size 10640 +oid sha256:5ab6a73fbda08c85580c7f326fb53ac3e51aa16ff6c16d95813e4feee29b5fb0 +size 11082 diff --git a/tests/Images/ReferenceOutput/RasterizerExtensionsTests/AntialiasingIsAntialiased_Rgba32_Solid400x75_(255,255,255,255).png b/tests/Images/ReferenceOutput/RasterizerExtensionsTests/AntialiasingIsAntialiased_Rgba32_Solid400x75_(255,255,255,255).png deleted file mode 100644 index b18af7e56..000000000 --- a/tests/Images/ReferenceOutput/RasterizerExtensionsTests/AntialiasingIsAntialiased_Rgba32_Solid400x75_(255,255,255,255).png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9950be5c4b9024969a96b8b30be5b30b6252366e492a14dd7f33db692453188d -size 397 diff --git a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank100x100_type-arrows.png b/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank100x100_type-arrows.png deleted file mode 100644 index 9993d5d5a..000000000 --- a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank100x100_type-arrows.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f7ff95b1daf10aaa3579fdfab07fb8ec570fe1f1ce4fb5f553d04f29dfda255 -size 407 diff --git a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank110x50_type-wave.png b/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank110x50_type-wave.png deleted file mode 100644 index f61f6ff2c..000000000 --- a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank110x50_type-wave.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b743c5edc9dc9478bdd8eeeea356b4c15c33942415eb0546cd2693453476eed1 -size 647 diff --git a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank110x70_type-zag.png b/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank110x70_type-zag.png deleted file mode 100644 index c1a2333a9..000000000 --- a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank110x70_type-zag.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4a58002ef2a2f39aee947a2cac4096e1dbeeb597564d049d2bec9de45585835 -size 470 diff --git a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-bumpy.png b/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-bumpy.png deleted file mode 100644 index 7fea71a75..000000000 --- a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-bumpy.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d45851a1743d5ebfda9cf3f6ba3f12627633954dea069a106dc9c01ee5458173 -size 4829 diff --git a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-chopped_oval.png b/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-chopped_oval.png deleted file mode 100644 index 429f4440c..000000000 --- a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-chopped_oval.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6503d5ecc224260ce158fbb8775293183220b9be20acf47bcfec1e4f482682ad -size 2746 diff --git a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-pie_big.png b/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-pie_big.png deleted file mode 100644 index 00af7f35d..000000000 --- a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-pie_big.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:16a17b87c0c302475c51472d93fa038dc39827317489c67f550a986450e35c98 -size 2428 diff --git a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-pie_small.png b/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-pie_small.png deleted file mode 100644 index cfbbe58a6..000000000 --- a/tests/Images/ReferenceOutput/SvgPath/RenderSvgPath_Rgba32_Blank500x400_type-pie_small.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:576d8476345f085444183cbcb8c61fd03c082113dfb32cc5d9f4859d86fc5be2 -size 4765 diff --git a/tests/Images/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_DoNotAppendPixelType_Solid10x10_(0,0,255,255).png b/tests/Images/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_DoNotAppendPixelType_Solid10x10_(0,0,255,255).png index 5d808e14f..1927b2a43 100644 --- a/tests/Images/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_DoNotAppendPixelType_Solid10x10_(0,0,255,255).png +++ b/tests/Images/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_DoNotAppendPixelType_Solid10x10_(0,0,255,255).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78036e25b5bfb3211e8794ec11d71e385401c8ff569e598a32152e7a8023eac9 -size 118 +oid sha256:2ccaca38823033a6aacde86619c7c959a79e65d36b5e8e94f42b762b47344f10 +size 82 diff --git a/tests/Images/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow_Rgba32_Solid10x10_(0,0,255,255).png b/tests/Images/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow_Rgba32_Solid10x10_(0,0,255,255).png index 5d808e14f..1927b2a43 100644 --- a/tests/Images/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow_Rgba32_Solid10x10_(0,0,255,255).png +++ b/tests/Images/ReferenceOutput/TestImageExtensionsTests/CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow_Rgba32_Solid10x10_(0,0,255,255).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78036e25b5bfb3211e8794ec11d71e385401c8ff569e598a32152e7a8023eac9 -size 118 +oid sha256:2ccaca38823033a6aacde86619c7c959a79e65d36b5e8e94f42b762b47344f10 +size 82 diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/00.png b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/00.png index e9052e9f5..9b752b4a7 100644 Binary files a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/00.png and b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/00.png differ diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/01.png b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/01.png index 4dc2e45d8..261476218 100644 Binary files a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/01.png and b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/01.png differ diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/02.png b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/02.png index b538aeaec..71a2732d8 100644 Binary files a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/02.png and b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/02.png differ diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/03.png b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/03.png index 18c3d2d59..9bb973b1e 100644 Binary files a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/03.png and b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/03.png differ diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/04.png b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/04.png index ed84340cb..540c0fe47 100644 Binary files a/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/04.png and b/tests/Images/ReferenceOutput/TestImageProviderTests/SaveTestOutputFileMultiFrame_Rgba32_giphy.gif/04.png differ diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern20x10.png b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern20x10.png index 381ff3db9..a57a541ad 100644 --- a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern20x10.png +++ b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern20x10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:883a948b8920fc6930f82b4985ab4fdf7d046a508221df88811a646a52036f11 -size 135 +oid sha256:35a69eb51a6954642789a80105a6133877f66c685c1564ae328fd18efceb2009 +size 121 diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern49x17.png b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern49x17.png index f2d513996..209a13c0b 100644 --- a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern49x17.png +++ b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern49x17.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e8795374bf53ed7585950437af71cbe6ab3ce5c6fda2c4da35566cb398333ac -size 155 +oid sha256:b541dadd48b1a136fed30442fd57fcb94f554c51f7869c2803e0ac72b98e97dc +size 122 diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern50x100.png b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern50x100.png index 74de36cb4..a56e0e6d7 100644 --- a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern50x100.png +++ b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithBasicTestPatternImages_Rgba32_BasicTestPattern50x100.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:015529debc61dd2eff6b61de0c107e6bdd87eb15bebe45b99f1d38b13242cec9 -size 219 +oid sha256:6ffc4784ea34bc5424363c9a5e9229bb2c1d330c49bac2a324db16c40b2f52b1 +size 132 diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Argb32_F.png b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Argb32_F.png index 464b8f0b0..69e8495e2 100644 --- a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Argb32_F.png +++ b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Argb32_F.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6773e8bfdcd78eea58c95c773a13a76b9e23dd0f16058675ade0d50786437fd1 -size 483 +oid sha256:c66b3009b2527c13e519c6cb9c86733e103a2b719b556c098380244d15a9a9e1 +size 183 diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Argb32_test8.png b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Argb32_test8.png index 3595cd1cc..81383beb7 100644 --- a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Argb32_test8.png +++ b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Argb32_test8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bf11d42f98951eb78873103c2bc51c690da6ff292e4f4ce25d9e30856b0f61c -size 404 +oid sha256:d3d0639717ff52db04200e00a75cf06490e55a661059511572954bec3d60dce9 +size 384 diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Rgba32_F.png b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Rgba32_F.png index 464b8f0b0..69e8495e2 100644 --- a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Rgba32_F.png +++ b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Rgba32_F.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6773e8bfdcd78eea58c95c773a13a76b9e23dd0f16058675ade0d50786437fd1 -size 483 +oid sha256:c66b3009b2527c13e519c6cb9c86733e103a2b719b556c098380244d15a9a9e1 +size 183 diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Rgba32_test8.png b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Rgba32_test8.png index 3595cd1cc..81383beb7 100644 --- a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Rgba32_test8.png +++ b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithFileCollection_Rgba32_test8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bf11d42f98951eb78873103c2bc51c690da6ff292e4f4ce25d9e30856b0f61c -size 404 +oid sha256:d3d0639717ff52db04200e00a75cf06490e55a661059511572954bec3d60dce9 +size 384 diff --git a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithTestPatternImages_Rgba32_TestPattern49x20.png b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithTestPatternImages_Rgba32_TestPattern49x20.png index 211f4f24d..c3ee13817 100644 --- a/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithTestPatternImages_Rgba32_TestPattern49x20.png +++ b/tests/Images/ReferenceOutput/TestImageProviderTests/Use_WithTestPatternImages_Rgba32_TestPattern49x20.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:952e58d9b70c65831d8d5c0aa151a7842e859e3519534d60d9e803492262aee6 -size 283 +oid sha256:3f809bac08fea0254309757288583ee82b8f7aa756e126e12b83730482fd8c03 +size 211 diff --git a/tests/coverlet.runsettings b/tests/coverlet.runsettings index 907d91489..455b7fe84 100644 --- a/tests/coverlet.runsettings +++ b/tests/coverlet.runsettings @@ -6,10 +6,11 @@ lcov [SixLabors.*]* - - - ^Clipper2Lib\..* - + + [SixLabors.ImageSharp.Drawing.WebGPU*]* true