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();
+ }
}
}