|
| 1 | +// Copyright (c) Six Labors. |
| 2 | +// Licensed under the Six Labors Split License. |
| 3 | + |
| 4 | +using SixLabors.Fonts; |
| 5 | +using SixLabors.ImageSharp.Drawing.Processing; |
| 6 | +using SixLabors.ImageSharp.PixelFormats; |
| 7 | + |
| 8 | +namespace SixLabors.ImageSharp.Drawing.Tests.Issues; |
| 9 | + |
| 10 | +public class Issue_397 |
| 11 | +{ |
| 12 | + [Theory] |
| 13 | + [WithBlankImage(240, 160, PixelTypes.Rgba32, BooleanOperation.Intersection)] |
| 14 | + [WithBlankImage(240, 160, PixelTypes.Rgba32, BooleanOperation.Union)] |
| 15 | + [WithBlankImage(240, 160, PixelTypes.Rgba32, BooleanOperation.Difference)] |
| 16 | + [WithBlankImage(240, 160, PixelTypes.Rgba32, BooleanOperation.Xor)] |
| 17 | + public void DrawTextWithIntersectingClip<TPixel>( |
| 18 | + TestImageProvider<TPixel> provider, |
| 19 | + BooleanOperation operation) |
| 20 | + where TPixel : unmanaged, IPixel<TPixel> |
| 21 | + { |
| 22 | + PointF textOrigin = new(54, 78); |
| 23 | + PointF clipCenter = new(104, 70); |
| 24 | + DrawingOptions clipOptions = CreateClipOptions(operation); |
| 25 | + Font font = TestFontUtilities.GetFont("OpenSans-Regular.ttf", 18); |
| 26 | + |
| 27 | + // Expected output: |
| 28 | + // - Intersection shows only red text inside the moved star. |
| 29 | + // - Difference shows only red text outside the moved star. |
| 30 | + // - Union and Xor can show a red star because the boolean-combined path includes the clip path, |
| 31 | + // and DrawText fills that combined result with the text brush. |
| 32 | + provider.RunValidatingProcessorTest( |
| 33 | + x => x.Paint(canvas => DrawIssue397Sample(canvas, clipOptions, clipCenter, textOrigin, font)), |
| 34 | + testOutputDetails: $"{operation}_IntersectingClip", |
| 35 | + appendPixelTypeToFileName: false, |
| 36 | + appendSourceFileOrDescription: false); |
| 37 | + } |
| 38 | + |
| 39 | + [Theory] |
| 40 | + [WithBlankImage(240, 160, PixelTypes.Rgba32, BooleanOperation.Intersection)] |
| 41 | + [WithBlankImage(240, 160, PixelTypes.Rgba32, BooleanOperation.Union)] |
| 42 | + [WithBlankImage(240, 160, PixelTypes.Rgba32, BooleanOperation.Difference)] |
| 43 | + [WithBlankImage(240, 160, PixelTypes.Rgba32, BooleanOperation.Xor)] |
| 44 | + public void DrawTextWithNonIntersectingClip<TPixel>( |
| 45 | + TestImageProvider<TPixel> provider, |
| 46 | + BooleanOperation operation) |
| 47 | + where TPixel : unmanaged, IPixel<TPixel> |
| 48 | + { |
| 49 | + PointF textOrigin = new(54, 78); |
| 50 | + PointF clipCenter = new(192, 116); |
| 51 | + DrawingOptions clipOptions = CreateClipOptions(operation); |
| 52 | + Font font = TestFontUtilities.GetFont("OpenSans-Regular.ttf", 18); |
| 53 | + |
| 54 | + // Expected output: |
| 55 | + // - Intersection shows no red text because the moved star and text do not overlap. |
| 56 | + // - Difference shows the full red text because the moved star removes nothing from it. |
| 57 | + // - Union and Xor show both the full red text and a red star because disjoint Xor matches Union, |
| 58 | + // and DrawText fills the boolean-combined result with the text brush. |
| 59 | + provider.RunValidatingProcessorTest( |
| 60 | + x => x.Paint(canvas => DrawIssue397Sample(canvas, clipOptions, clipCenter, textOrigin, font)), |
| 61 | + testOutputDetails: $"{operation}_NonIntersectingClip", |
| 62 | + appendPixelTypeToFileName: false, |
| 63 | + appendSourceFileOrDescription: false); |
| 64 | + } |
| 65 | + |
| 66 | + private static void DrawIssue397Sample( |
| 67 | + DrawingCanvas canvas, |
| 68 | + DrawingOptions clipOptions, |
| 69 | + PointF clipCenter, |
| 70 | + PointF textOrigin, |
| 71 | + Font font) |
| 72 | + { |
| 73 | + canvas.Clear(Brushes.Solid(Color.White)); |
| 74 | + StarPolygon clipPath = new(clipCenter, 7, 16, 38, 18); |
| 75 | + RichTextOptions textOptions = new(font) |
| 76 | + { |
| 77 | + Origin = textOrigin |
| 78 | + }; |
| 79 | + |
| 80 | + // The gray outline is the unclipped text guide; the red draw below shows the boolean clip result. |
| 81 | + canvas.DrawText(textOptions, "This is a test", brush: null, Pens.Solid(Color.LightGray, 1F)); |
| 82 | + |
| 83 | + // The blue outline marks the moved clipping path without adding a filled shape behind the text. |
| 84 | + canvas.Draw(Pens.Solid(Color.DarkBlue, 1F), clipPath); |
| 85 | + canvas.Save(clipOptions, clipPath); |
| 86 | + |
| 87 | + canvas.DrawText( |
| 88 | + textOptions, |
| 89 | + "This is a test", |
| 90 | + Brushes.Solid(Color.Crimson), |
| 91 | + pen: null); |
| 92 | + |
| 93 | + canvas.Restore(); |
| 94 | + canvas.Draw(Pens.Solid(Color.DarkBlue, 1F), clipPath); |
| 95 | + } |
| 96 | + |
| 97 | + private static DrawingOptions CreateClipOptions(BooleanOperation operation) |
| 98 | + => new() |
| 99 | + { |
| 100 | + ShapeOptions = new() |
| 101 | + { |
| 102 | + BooleanOperation = operation |
| 103 | + } |
| 104 | + }; |
| 105 | +} |
0 commit comments