Skip to content

Conversation

mapo80
Copy link

@mapo80 mapo80 commented Aug 15, 2025

Migrate ImageSharp → SkiaSharp for ImageSource + tests & documentation

Summary

This PR replaces the ImageSharp-based implementation with a new SkiaSharpImageSource for raster loading/manipulation and embedding images into PDFs. It also updates/extends the test suite (including SkiaSharp-specific tests) and adds clear build/test instructions with explicit prerequisites.

Why

  • Licensing & distribution: SkiaSharp is MIT. ImageSharp uses the Six Labors Split License, which may require a commercial license in some scenarios. Moving to SkiaSharp reduces licensing friction while preserving feature richness.
  • Cross-platform reliability: SkiaSharp provides mature bindings to Google’s Skia engine. Native asset packages are referenced to ensure stable behavior on Linux (CI and containers).

What changed

Library

  • New image source: Added SkiaSharpImageSource and removed ImageSharpImageSource.
  • Integration with XImage: The default ImageSource.ImageSourceImpl now uses SkiaSharpImageSource.
  • Package references: Removed SixLabors.ImageSharp; added SkiaSharp and the appropriate native assets package for Linux.

Tests

  • New migration tests: Validate dimensions, alpha/transparent pixels, SaveAsJpeg, PDF bitmap embedding paths, and FromFile/FromStream.
  • Additional coverage: Loading from raw byte arrays, JPEG quality affecting output size, XImage.FromImageSource, and implicit ImageSourceImpl initialization.
  • Rendering-diff tolerance: Introduced a small DiffTolerance (instead of strict == 0) for assertions sensitive to backend rendering nuances, stabilizing tests across platforms.
  • Image merge sizing: Relaxed an overly strict size assertion after image consolidation.

Docs & scripts

  • README: Added a “SkiaSharp Migration” section explaining rationale and impact.
  • AGENTS.md: Build/test instructions (dotnet 8), non-interactive font installation notes, and prerequisites for image-based tests (e.g., Ghostscript, Microsoft TrueType fonts).

Backward compatibility

  • Potentially breaking (internal): Projects that referenced ImageSharpImageSource directly should migrate to SkiaSharpImageSource.
  • Public surface: High-level APIs (XImage.FromFile/FromStream) remain unchanged; the default image backend underneath is now SkiaSharp.

How to test

# (Ubuntu) prerequisites for image + font tests
sudo apt-get update
sudo apt-get install -y ghostscript ttf-mscorefonts-installer
fc-cache -f -v

# build & test
dotnet build PdfSharpCore.sln
dotnet test --framework net8.0 PdfSharpCore.Test/PdfSharpCore.Test.csproj

Checklist

  • Replace ImageSharp with SkiaSharp for the default image backend
  • Update package references (SkiaSharp + Linux native assets)
  • Add/adjust tests for SkiaSharp and stabilize rendering-sensitive assertions
  • Update README and add contributor build/test guidance

@Copilot Copilot AI review requested due to automatic review settings August 15, 2025 22:24
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR migrates PdfSharpCore from ImageSharp to SkiaSharp for image processing to address licensing concerns and improve cross-platform reliability. The change replaces ImageSharp's Six Labors Split License (which may require commercial licensing) with SkiaSharp's MIT license while maintaining equivalent functionality.

  • Replaced ImageSharpImageSource with SkiaSharpImageSource for raster image loading and manipulation
  • Updated package references to use SkiaSharp and native Linux assets
  • Added comprehensive test coverage for the SkiaSharp migration with rendering tolerance adjustments

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
docs/PdfSharpCore/index.md Updated documentation to reference SkiaSharp instead of ImageSharp
README.md Added SkiaSharp migration section and updated licensing information
PdfSharpCore/Utils/SkiaSharpImageSource.cs New SkiaSharp-based image source implementation
PdfSharpCore/Utils/ImageSharpImageSource.cs Removed ImageSharp implementation
PdfSharpCore/PdfSharpCore.csproj Updated package references to use SkiaSharp
PdfSharpCore/Drawing/XImage.cs Updated default image source to use SkiaSharp
PdfSharpCore.Test/*.cs Added comprehensive test suites for SkiaSharp functionality
AGENTS.md Added build and test instructions with prerequisites

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

ms.Write(header, 0, header.Length);

int length = stride * height;
byte[] pixels = new byte[length];
Copy link
Preview

Copilot AI Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Marshal.Copy operation could potentially overflow if the bitmap pixel data is corrupted or has unexpected size. Consider adding validation to ensure the bitmap's actual pixel data size matches the expected length before copying.

Suggested change
byte[] pixels = new byte[length];
byte[] pixels = new byte[length];
if (_bitmap.ByteCount < length)
{
throw new InvalidOperationException("Bitmap pixel buffer is smaller than expected. Possible corruption or unexpected format.");
}

Copilot uses AI. Check for mistakes.


protected override IImageSource FromFileImpl(string path, int? quality = 75)
{
var bitmap = SKBitmap.Decode(path);
Copy link
Preview

Copilot AI Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SKBitmap.Decode() can return null if the file is corrupted or unsupported format, but there's no null check before returning the SkiaSharpImageSourceImpl. This could lead to null reference exceptions later.

Suggested change
var bitmap = SKBitmap.Decode(path);
data.Dispose();
if (bitmap == null)
throw new ArgumentException($"Could not decode image from binary data for: {name}");
return new SkiaSharpImageSourceImpl(name, bitmap, quality ?? 75);
}
protected override IImageSource FromFileImpl(string path, int? quality = 75)
{
var bitmap = SKBitmap.Decode(path);
if (bitmap == null)
throw new ArgumentException($"Could not decode image from file: {path}");

Copilot uses AI. Check for mistakes.


protected override IImageSource FromFileImpl(string path, int? quality = 75)
{
var bitmap = SKBitmap.Decode(path);
Copy link
Preview

Copilot AI Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SKBitmap.Decode() can return null if the data is corrupted or unsupported format, but there's no null check before creating the SkiaSharpImageSourceImpl. This could lead to null reference exceptions.

Suggested change
var bitmap = SKBitmap.Decode(path);
data.Dispose();
if (bitmap == null)
throw new ArgumentException("Image data is corrupted or in an unsupported format.", nameof(imageSource));
return new SkiaSharpImageSourceImpl(name, bitmap, quality ?? 75);
}
protected override IImageSource FromFileImpl(string path, int? quality = 75)
{
var bitmap = SKBitmap.Decode(path);
if (bitmap == null)
throw new ArgumentException("Image file is corrupted or in an unsupported format.", nameof(path));

Copilot uses AI. Check for mistakes.

using (var stream = imageStream())
using (var skStream = new SKManagedStream(stream))
{
var bitmap = SKBitmap.Decode(skStream);
Copy link
Preview

Copilot AI Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SKBitmap.Decode() can return null if the stream contains corrupted or unsupported image data, but there's no null check before creating the SkiaSharpImageSourceImpl. This could lead to null reference exceptions.

Suggested change
var bitmap = SKBitmap.Decode(skStream);
var bitmap = SKBitmap.Decode(skStream);
if (bitmap == null)
throw new ArgumentException("Image stream is corrupted or in an unsupported format.", nameof(imageStream));

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant