diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/Documents/blend_opacity.pdf b/UglyToad.PdfPig.Rendering.Skia.Tests/Documents/blend_opacity.pdf new file mode 100644 index 0000000..6c89148 Binary files /dev/null and b/UglyToad.PdfPig.Rendering.Skia.Tests/Documents/blend_opacity.pdf differ diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/11194059_2017-11_de_s_1.png b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/11194059_2017-11_de_s_1.png index 9330338..e229bee 100644 Binary files a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/11194059_2017-11_de_s_1.png and b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/11194059_2017-11_de_s_1.png differ diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/22060_A1_01_Plans-1_1.png b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/22060_A1_01_Plans-1_1.png index 3a9da16..7c56c7c 100644 Binary files a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/22060_A1_01_Plans-1_1.png and b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/22060_A1_01_Plans-1_1.png differ diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/GHOSTSCRIPT-697507-0_1.png b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/GHOSTSCRIPT-697507-0_1.png index 4e801de..0b1c726 100644 Binary files a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/GHOSTSCRIPT-697507-0_1.png and b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/GHOSTSCRIPT-697507-0_1.png differ diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/blend_opacity_1.png b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/blend_opacity_1.png new file mode 100644 index 0000000..9d1045e Binary files /dev/null and b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/blend_opacity_1.png differ diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/cat-genetics_bobld_1.png b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/cat-genetics_bobld_1.png index fb83025..0c7600c 100644 Binary files a/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/cat-genetics_bobld_1.png and b/UglyToad.PdfPig.Rendering.Skia.Tests/ExpectedImages/pdfpig_skia/cat-genetics_bobld_1.png differ diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/PdfToImageHelper.cs b/UglyToad.PdfPig.Rendering.Skia.Tests/PdfToImageHelper.cs index 0aaf367..d862d70 100644 --- a/UglyToad.PdfPig.Rendering.Skia.Tests/PdfToImageHelper.cs +++ b/UglyToad.PdfPig.Rendering.Skia.Tests/PdfToImageHelper.cs @@ -24,7 +24,7 @@ public static class PdfToImageHelper private static SKBitmap createEmptyDiffImage(int minWidth, int minHeight, int maxWidth, int maxHeight) { - using (SKBitmap bim3 = new SKBitmap(new SKImageInfo(maxWidth, maxHeight, SKColorType.Rgb888x))) + var bim3 = new SKBitmap(new SKImageInfo(maxWidth, maxHeight, SKColorType.Rgb888x)); using (SKCanvas canvas = new SKCanvas(bim3)) { if (minWidth != maxWidth || minHeight != maxHeight) @@ -45,14 +45,14 @@ private static SKBitmap createEmptyDiffImage(int minWidth, int minHeight, int ma private const byte _threshold = 2; - private static SKBitmap diffImages(SKBitmap bim1, SKBitmap bim2) + private static SKBitmap? diffImages(SKBitmap bim1, SKBitmap bim2) { int minWidth = Math.Min(bim1.Width, bim2.Width); int minHeight = Math.Min(bim1.Height, bim2.Height); int maxWidth = Math.Max(bim1.Width, bim2.Width); int maxHeight = Math.Max(bim1.Height, bim2.Height); - SKBitmap bim3 = null; + SKBitmap? bim3 = null; if (minWidth != maxWidth || minHeight != maxHeight) { bim3 = createEmptyDiffImage(minWidth, minHeight, maxWidth, maxHeight); @@ -121,7 +121,7 @@ public static bool TestResizeSinglePage(string pdfFile, int pageNumber, string e using (var document = PdfDocument.Open(docPath, SkiaRenderingParsingOptions.Instance)) { document.AddSkiaPageFactory(); - using (var actual = document.GetPageAsSKBitmap(pageNumber, scale)) + using (var actual = document.GetPageAsSKBitmap(pageNumber, scale, SKColors.White)) { var skInfo = new SKImageInfo() { @@ -150,7 +150,7 @@ public static bool TestResizeSinglePage(string pdfFile, int pageNumber, string e return true; } - SKBitmap bim3 = diffImages(expectedResize, actualResize); + using var bim3 = diffImages(expectedResize, actualResize); if (bim3 is null) { return true; @@ -189,7 +189,7 @@ public static bool TestSinglePage(string pdfFile, int pageNumber, string expecte using (var document = PdfDocument.Open(docPath, SkiaRenderingParsingOptions.Instance)) { document.AddSkiaPageFactory(); - using (var actual = document.GetPageAsSKBitmap(pageNumber, scale)) + using (var actual = document.GetPageAsSKBitmap(pageNumber, scale, SKColors.White)) { if (filesAreIdentical(expected, actual)) { diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/TestRendering.cs b/UglyToad.PdfPig.Rendering.Skia.Tests/TestRendering.cs index 840f9d1..575fe4f 100644 --- a/UglyToad.PdfPig.Rendering.Skia.Tests/TestRendering.cs +++ b/UglyToad.PdfPig.Rendering.Skia.Tests/TestRendering.cs @@ -504,6 +504,11 @@ public class TestRendering "GHOSTSCRIPT-698721-1-rotated_1.png", "GHOSTSCRIPT-698721-1-rotated.pdf", 1, 2 }, + new object[] + { + "blend_opacity_1.png", + "blend_opacity.pdf", 1, 2 + }, }; [Theory(Skip = "for debugging purpose.")] diff --git a/UglyToad.PdfPig.Rendering.Skia.Tests/UglyToad.PdfPig.Rendering.Skia.Tests.csproj b/UglyToad.PdfPig.Rendering.Skia.Tests/UglyToad.PdfPig.Rendering.Skia.Tests.csproj index 856dbb9..b2384d0 100644 --- a/UglyToad.PdfPig.Rendering.Skia.Tests/UglyToad.PdfPig.Rendering.Skia.Tests.csproj +++ b/UglyToad.PdfPig.Rendering.Skia.Tests/UglyToad.PdfPig.Rendering.Skia.Tests.csproj @@ -32,6 +32,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -365,6 +368,9 @@ Always + + Always + Always diff --git a/UglyToad.PdfPig.Rendering.Skia/Helpers/SKPaintCache.cs b/UglyToad.PdfPig.Rendering.Skia/Helpers/SKPaintCache.cs index b7987e0..e9d0aa6 100644 --- a/UglyToad.PdfPig.Rendering.Skia/Helpers/SKPaintCache.cs +++ b/UglyToad.PdfPig.Rendering.Skia/Helpers/SKPaintCache.cs @@ -26,9 +26,7 @@ internal sealed class SKPaintCache : IDisposable private readonly bool _isAntialias; private readonly Dictionary _cache = new(); - - private readonly SKPaint _antialiasingPaint; - private readonly SKPaint _noAntialiasingPaint; + private readonly Dictionary<(bool, BlendMode), SKPaint> _imagePaintCache = new(); #if DEBUG private readonly SKPaint _imageDebugPaint; @@ -38,9 +36,7 @@ public SKPaintCache(bool isAntialias, float minimumLineWidth) { _isAntialias = isAntialias; // minimumLineWidth not in use - _antialiasingPaint = new SKPaint() { IsAntialias = true }; - _noAntialiasingPaint = new SKPaint() { IsAntialias = false }; #if DEBUG _imageDebugPaint = new SKPaint() { @@ -53,15 +49,15 @@ public SKPaintCache(bool isAntialias, float minimumLineWidth) } private static int GetPaintKey(IColor color, double alpha, bool stroke, float? strokeWidth, LineJoinStyle? joinStyle, - LineCapStyle? capStyle, LineDashPattern? dashPattern) + LineCapStyle? capStyle, LineDashPattern? dashPattern, BlendMode blendMode) { - return HashCode.Combine(color, alpha, stroke, strokeWidth, joinStyle, capStyle, GetHash(dashPattern)); + return HashCode.Combine(color, alpha, stroke, strokeWidth, joinStyle, capStyle, GetHash(dashPattern), blendMode); } public SKPaint GetPaint(IColor color, double alpha, bool stroke, float? strokeWidth, LineJoinStyle? joinStyle, - LineCapStyle? capStyle, LineDashPattern? dashPattern) + LineCapStyle? capStyle, LineDashPattern? dashPattern, BlendMode blendMode) { - var key = GetPaintKey(color, alpha, stroke, strokeWidth, joinStyle, capStyle, dashPattern); + var key = GetPaintKey(color, alpha, stroke, strokeWidth, joinStyle, capStyle, dashPattern, blendMode); if (_cache.TryGetValue(key, out var paint)) { @@ -73,6 +69,7 @@ public SKPaint GetPaint(IColor color, double alpha, bool stroke, float? strokeWi IsAntialias = _isAntialias, Color = color.ToSKColor(alpha), Style = stroke ? SKPaintStyle.Stroke : SKPaintStyle.Fill, + BlendMode = blendMode.ToSKBlendMode() }; if (stroke) @@ -104,18 +101,25 @@ private static int GetHash(LineDashPattern? dashPattern) return key; } - public SKPaint GetPaint(IPdfImage pdfImage) + public SKPaint GetPaint(IPdfImage pdfImage, BlendMode blendMode) { - if (!pdfImage.Interpolate) + // For non-Normal blend modes, use general cache with ValueTuple key + var key = (pdfImage.Interpolate, blendMode); + + if (_imagePaintCache.TryGetValue(key, out var paint)) { - return _noAntialiasingPaint; + return paint; } - return _antialiasingPaint; - } - public SKPaint GetAntialiasing() - { - return _antialiasingPaint; + paint = new SKPaint + { + IsAntialias = pdfImage.Interpolate, + BlendMode = blendMode.ToSKBlendMode() + }; + + _imagePaintCache[key] = paint; + + return paint; } #if DEBUG @@ -130,15 +134,18 @@ public void Dispose() #if DEBUG _imageDebugPaint.Dispose(); #endif - _antialiasingPaint.Dispose(); - _noAntialiasingPaint.Dispose(); - foreach (var pair in _cache) { pair.Value.PathEffect?.Dispose(); pair.Value.Dispose(); } _cache.Clear(); + + foreach (var pair in _imagePaintCache) + { + pair.Value.Dispose(); + } + _imagePaintCache.Clear(); } } } diff --git a/UglyToad.PdfPig.Rendering.Skia/Helpers/SkiaExtensions.cs b/UglyToad.PdfPig.Rendering.Skia/Helpers/SkiaExtensions.cs index 81730a0..310d959 100644 --- a/UglyToad.PdfPig.Rendering.Skia/Helpers/SkiaExtensions.cs +++ b/UglyToad.PdfPig.Rendering.Skia/Helpers/SkiaExtensions.cs @@ -275,14 +275,8 @@ public static SKMatrix ToSkMatrix(this TransformationMatrix transformationMatrix 0, 0, 1); } - private static readonly bool doBlending = false; - public static SKBlendMode ToSKBlendMode(this BlendMode blendMode) { - if (!doBlending) - { - return SKBlendMode.SrcOver; - } // https://pdfium.googlesource.com/pdfium/+/refs/heads/main/core/fxge/skia/fx_skia_device.cpp switch (blendMode) diff --git a/UglyToad.PdfPig.Rendering.Skia/PdfPigExtensions.cs b/UglyToad.PdfPig.Rendering.Skia/PdfPigExtensions.cs index e5c4706..5d1f400 100644 --- a/UglyToad.PdfPig.Rendering.Skia/PdfPigExtensions.cs +++ b/UglyToad.PdfPig.Rendering.Skia/PdfPigExtensions.cs @@ -39,17 +39,23 @@ public static void AddSkiaPageFactory(this PdfDocument document) /// The pdf document. /// The number of the page to return, this starts from 1. /// The scale factor to use when rendering the page. + /// Optional background color to clear the canvas with before rendering. If null, the canvas is not cleared. /// The . - public static SKBitmap GetPageAsSKBitmap(this PdfDocument document, int pageNumber, float scale = 1) + public static SKBitmap GetPageAsSKBitmap(this PdfDocument document, int pageNumber, float scale = 1, SKColor? clearColor = null) { using (var picture = document.GetPage(pageNumber)) { - var size = new SKSizeI((int)Math.Ceiling(picture.CullRect.Width * scale), (int)Math.Ceiling(picture.CullRect.Height * scale)); + var page = document.GetPage(pageNumber); + var size = new SKSizeI((int)Math.Ceiling(page.Width * scale), (int)Math.Ceiling(page.Height * scale)); var scaleMatrix = SKMatrix.CreateScale(scale, scale); var bitmap = new SKBitmap(size.Width, size.Height); using (var canvas = new SKCanvas(bitmap)) { + if (clearColor.HasValue) + { + canvas.Clear(clearColor.Value); + } canvas.DrawPicture(picture, in scaleMatrix); return bitmap; } @@ -67,7 +73,7 @@ public static SKBitmap GetPageAsSKBitmap(this PdfDocument document, int pageNumb public static MemoryStream GetPageAsPng(this PdfDocument document, int pageNumber, float scale = 1, int quality = 100) { var ms = new MemoryStream(); - using (var bitmap = document.GetPageAsSKBitmap(pageNumber, scale)) + using (var bitmap = document.GetPageAsSKBitmap(pageNumber, scale, SKColors.White)) { bitmap.Encode(ms, SKEncodedImageFormat.Png, quality); ms.Position = 0; diff --git a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Annotations.cs b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Annotations.cs index 35a55cf..add9d46 100644 --- a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Annotations.cs +++ b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Annotations.cs @@ -42,19 +42,6 @@ internal partial class SkiaStreamProcessor /// public static readonly RGBColor DefaultRequiredFieldsHighlightColor = new RGBColor(1, 0, 0); - private static bool IsAnnotationBelowText(Annotation annotation) - { - // TODO - Very hackish - switch (annotation.Type) - { - case AnnotationType.Highlight: - return true; - - default: - return false; - } - } - private static bool ShouldRender(Annotation annotation) { // cf. ISO 32000-2:2020(E) - Table 167 — Annotation flags @@ -70,11 +57,11 @@ private static bool ShouldRender(Annotation annotation) private readonly Lazy _annotations; - private void DrawAnnotations(bool isBelowText) + private void DrawAnnotations() { // https://github.com/apache/pdfbox/blob/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java // https://github.com/apache/pdfbox/blob/c4b212ecf42a1c0a55529873b132ea338a8ba901/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDFStreamEngine.java#L312 - foreach (Annotation annotation in _annotations.Value.Where(a => IsAnnotationBelowText(a) == isBelowText)) + foreach (Annotation annotation in _annotations.Value) { // Check if visible if (!ShouldRender(annotation)) diff --git a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Glyph.cs b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Glyph.cs index cda2f7d..4832531 100644 --- a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Glyph.cs +++ b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Glyph.cs @@ -126,7 +126,7 @@ private void ShowVectorFontGlyph(SKPath path, IColor strokingColor, IColor nonSt else { var fillBrush = _paintCache.GetPaint(nonStrokingColor, currentState.AlphaConstantNonStroking, false, - null, null, null, null); + null, null, null, null, currentState.BlendMode); _canvas.DrawPath(transformedPath, fillBrush); } } @@ -136,7 +136,7 @@ private void ShowVectorFontGlyph(SKPath path, IColor strokingColor, IColor nonSt // Then stroke var strokePaint = _paintCache.GetPaint(strokingColor, currentState.AlphaConstantStroking, true, (float)currentState.LineWidth, currentState.JoinStyle, currentState.CapStyle, - currentState.LineDashPattern); + currentState.LineDashPattern, currentState.BlendMode); _canvas.DrawPath(transformedPath, strokePaint); } } @@ -181,12 +181,15 @@ private void ShowNonVectorFontGlyph(IFont font, IColor strokingColor, IColor non var color = style == SKPaintStyle.Stroke ? strokingColor : nonStrokingColor; // TODO - very not correct + var currentState = GetCurrentState(); + using (var skFont = drawTypeface.Typeface.ToFont(1f)) using (var paint = new SKPaint()) { paint.Style = style.Value; - paint.Color = color.ToSKColor(GetCurrentState().AlphaConstantNonStroking); + paint.Color = color.ToSKColor(currentState.AlphaConstantNonStroking); paint.IsAntialias = _antiAliasing; + paint.BlendMode = currentState.BlendMode.ToSKBlendMode(); // TODO - Benchmark with SPARC - v9 Architecture Manual.pdf // as _canvas.DrawShapedText(unicode, startBaseLine, fontPaint); as very slow without 'Shaper' caching diff --git a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Image.cs b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Image.cs index 7f83a83..96cf31f 100644 --- a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Image.cs +++ b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Image.cs @@ -47,60 +47,62 @@ private void RenderImage(IPdfImage pdfImage) try { - using (new SKAutoCanvasRestore(_canvas, true)) - using (var bitmap = pdfImage.GetSKBitmap(ParsingOptions.Logger)) + using SKAutoCanvasRestore skAutoCanvasRestore = new SKAutoCanvasRestore(_canvas, true); + using var bitmap = pdfImage.GetSKBitmap(ParsingOptions.Logger); + + if (bitmap is null) { - if (bitmap is null) - { - throw new NullReferenceException("Got a null image."); - } + throw new NullReferenceException("Got a null image."); + } - bitmap.SetImmutable(); + bitmap.SetImmutable(); - // Images are upside down in PDF - _canvas.Scale(1, -1, 0, 0.5f); + // Images are upside down in PDF + _canvas.Scale(1, -1, 0, 0.5f); - if (!pdfImage.IsImageMask) - { - _canvas.DrawBitmap(bitmap, new SKRect(0, 0, 1, 1), _paintCache.GetPaint(pdfImage)); - } - else - { - // Draw image mask - var currentState = GetCurrentState(); - SKColor colour = currentState.CurrentNonStrokingColor - .ToSKColor(currentState.AlphaConstantNonStroking); + var currentState = GetCurrentState(); + + if (!pdfImage.IsImageMask) + { + var imagePaint = _paintCache.GetPaint(pdfImage, currentState.BlendMode); + using SKImage image = SKImage.FromBitmap(bitmap); + _canvas.DrawImage(image, new SKRect(0, 0, 1, 1), SKSamplingOptions.Default, imagePaint); + } + else + { + // Draw image mask + SKColor colour = currentState.CurrentNonStrokingColor + .ToSKColor(currentState.AlphaConstantNonStroking); - byte r = colour.Red; - byte g = colour.Green; - byte b = colour.Blue; - byte a = byte.MaxValue; // TODO - Use colour.Alpha? + byte r = colour.Red; + byte g = colour.Green; + byte b = colour.Blue; + byte a = byte.MaxValue; // TODO - Use colour.Alpha? + + using SKBitmap maskedBitmap = new SKBitmap(bitmap.Width, bitmap.Height, SKColorType.Rgba8888, SKAlphaType.Premul); + Span rasterSpan = maskedBitmap.GetPixelSpan(); + Span span = bitmap.GetPixelSpan(); - using (SKBitmap maskedBitmap = new SKBitmap(bitmap.Width, bitmap.Height, SKColorType.Rgba8888, SKAlphaType.Premul)) + for (int row = 0; row < bitmap.Height; ++row) + { + for (int col = 0; col < bitmap.Width; ++col) { - Span rasterSpan = maskedBitmap.GetPixelSpan(); - Span span = bitmap.GetPixelSpan(); - - for (int row = 0; row < bitmap.Height; ++row) - { - for (int col = 0; col < bitmap.Width; ++col) - { - byte pixel = span[(row * bitmap.Width) + col]; - if (pixel == byte.MinValue) - { - var start = (row * (bitmap.Width * 4)) + (col * 4); - rasterSpan[start] = r; - rasterSpan[start + 1] = g; - rasterSpan[start + 2] = b; - rasterSpan[start + 3] = a; - } - } - } - - maskedBitmap.SetImmutable(); - _canvas.DrawBitmap(maskedBitmap, new SKRect(0, 0, 1, 1), _paintCache.GetPaint(pdfImage)); + byte pixel = span[(row * bitmap.Width) + col]; + if (pixel != byte.MinValue) + continue; + + var start = (row * (bitmap.Width * 4)) + (col * 4); + rasterSpan[start] = r; + rasterSpan[start + 1] = g; + rasterSpan[start + 2] = b; + rasterSpan[start + 3] = a; } } + + maskedBitmap.SetImmutable(); + var maskPaint = _paintCache.GetPaint(pdfImage, currentState.BlendMode); + using SKImage image = SKImage.FromBitmap(maskedBitmap); + _canvas.DrawImage(image, new SKRect(0, 0, 1, 1), SKSamplingOptions.Default, maskPaint); } return; diff --git a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Path.cs b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Path.cs index 5632889..53d7a10 100644 --- a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Path.cs +++ b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Path.cs @@ -160,7 +160,7 @@ private void PaintStrokePath(CurrentGraphicsState currentState) { var paint = _paintCache.GetPaint(currentState.CurrentStrokingColor, currentState.AlphaConstantStroking, true, (float)currentState.LineWidth, currentState.JoinStyle, currentState.CapStyle, - currentState.LineDashPattern); + currentState.LineDashPattern, currentState.BlendMode); _canvas.DrawPath(_currentPath, paint); } } @@ -215,7 +215,7 @@ private void PaintFillPath(CurrentGraphicsState currentState, FillingRule fillin else { var paint = _paintCache.GetPaint(currentState.CurrentNonStrokingColor, - currentState.AlphaConstantNonStroking, false, null, null, null, null); + currentState.AlphaConstantNonStroking, false, null, null, null, null, currentState.BlendMode); _canvas.DrawPath(_currentPath, paint); /* No cache method diff --git a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Shading.cs b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Shading.cs index 5892f0c..24974e5 100644 --- a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Shading.cs +++ b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Shading.cs @@ -67,7 +67,7 @@ private void RenderUnsupportedShading(Shading shading, in SKMatrix patternTransf { paint.IsAntialias = shading.AntiAlias; paint.Shader = shader; - //paint.BlendMode = GetCurrentState().BlendMode.ToSKBlendMode(); // TODO - check if correct + paint.BlendMode = GetCurrentState().BlendMode.ToSKBlendMode(); // check if bbox not null @@ -154,9 +154,11 @@ private void RenderRadialShading(RadialShading shading, in SKMatrix patternTrans { paint.IsAntialias = shading.AntiAlias; paint.Shader = shader; + paint.BlendMode = currentState.BlendMode.ToSKBlendMode(); // check if bbox not null + SKPathEffect? dash = null; if (isStroke) { // TODO - To finish @@ -164,7 +166,8 @@ private void RenderRadialShading(RadialShading shading, in SKMatrix patternTrans paint.StrokeWidth = (float)currentState.LineWidth; paint.StrokeJoin = currentState.JoinStyle.ToSKStrokeJoin(); paint.StrokeCap = currentState.CapStyle.ToSKStrokeCap(); - paint.PathEffect = currentState.LineDashPattern.ToSKPathEffect(); + dash = currentState.LineDashPattern.ToSKPathEffect(); + paint.PathEffect = dash; } if (path is null) @@ -175,6 +178,8 @@ private void RenderRadialShading(RadialShading shading, in SKMatrix patternTrans { _canvas.DrawPath(path, paint); } + + dash?.Dispose(); } } @@ -226,6 +231,7 @@ private void RenderAxialShading(AxialShading shading, in SKMatrix patternTransfo { paint.IsAntialias = shading.AntiAlias; paint.Shader = shader; + paint.BlendMode = currentState.BlendMode.ToSKBlendMode(); // check if bbox not null @@ -343,23 +349,25 @@ private void RenderFunctionBasedShading(FunctionBasedShading shading, in SKMatri var finalShadingMatrix = patternTransformMatrix.PreConcat(shading.Matrix.ToSkMatrix()); + var currentState = GetCurrentState(); + using (var shader = SKShader.CreateBitmap(shaderBitmap, SKShaderTileMode.Decal, SKShaderTileMode.Decal, finalShadingMatrix)) using (var paint = new SKPaint()) { paint.IsAntialias = shading.AntiAlias; paint.Shader = shader; + paint.BlendMode = currentState.BlendMode.ToSKBlendMode(); SKPathEffect? dash = null; if (isStroke) { - var currentState = GetCurrentState(); - // TODO - To Check paint.Style = SKPaintStyle.Stroke; paint.StrokeWidth = (float)currentState.LineWidth; paint.StrokeJoin = currentState.JoinStyle.ToSKStrokeJoin(); paint.StrokeCap = currentState.CapStyle.ToSKStrokeCap(); - paint.PathEffect = currentState.LineDashPattern.ToSKPathEffect(); + dash = currentState.LineDashPattern.ToSKPathEffect(); + paint.PathEffect = dash; } if (path is null) @@ -485,6 +493,7 @@ private void RenderTilingPattern(SKPath path, TilingPatternColor pattern, bool i { paint.IsAntialias = _antiAliasing; paint.Shader = shader; + paint.BlendMode = GetCurrentState().BlendMode.ToSKBlendMode(); //#if DEBUG // _canvas.DrawPath(path, new SKPaint() { Color = SKColors.Blue.WithAlpha(150) }); diff --git a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.cs b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.cs index b2d9ed2..ee62336 100644 --- a/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.cs +++ b/UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.cs @@ -45,6 +45,12 @@ internal partial class SkiaStreamProcessor : BaseStreamProcessor // Stack to keep track of original transforms for nested form XObjects private readonly Stack _currentStreamOriginalTransforms = new(); + // Stack to track layer paints for transparency groups (null means regular Save was used) + private readonly Stack _transparencyLayerPaints = new(); + + // Pending layer paint to be used in the next PushState call + private SKPaint? _pendingTransparencyLayerPaint; + private SKCanvas _canvas; private bool _updateCurrentStreamOriginalTransform; @@ -92,39 +98,31 @@ public override SKPicture Process(int pageNumberCurrent, IReadOnlyList(NameToken.S, PdfScanner, out var sToken) + && sToken == NameToken.Transparency; + + if (isTransparencyGroup) + { + // Capture parent state's blend mode and alpha before PushState + var parentState = GetCurrentState(); + + // Set pending layer paint - will be used in PushState called by base.ProcessFormXObject + // The transparency group will be composited with parent's blend mode and alpha when the layer is restored + _pendingTransparencyLayerPaint = new SKPaint + { + IsAntialias = _antiAliasing, + BlendMode = parentState.BlendMode.ToSKBlendMode(), + Color = SKColors.White.WithAlpha((byte)(parentState.AlphaConstantNonStroking * 255)) + }; + } + // Indicate that we want to update the original transform for form XObject _updateCurrentStreamOriginalTransform = true; @@ -197,5 +233,17 @@ protected override void ProcessFormXObject(StreamToken formStream, NameToken xOb // Restore previous original transform _currentStreamOriginalTransforms.Pop(); } + + private void Cleanup() + { + _paintCache.Dispose(); + _pendingTransparencyLayerPaint?.Dispose(); + _pendingTransparencyLayerPaint = null; + foreach (var paint in _transparencyLayerPaints) + { + paint?.Dispose(); + } + _transparencyLayerPaints.Clear(); + } } }