Replace ImageSharp with Magick.NET in the fixture generator

ImageSharp 4.x / ImageSharp.Drawing 3.x add a build-time license check
that fails Release builds without a Six Labors license key, and the
older 3.x / 2.x line is the last one without it. Magick.NET is Apache
2.0, bundles the native ImageMagick library for win/linux/osx, and
covers every encoder the fixtures need (PNG/BMP/JPG/GIF), so the
generator moves to it instead of carrying a license key.

The checked-in binary fixtures are regenerated with the new encoders;
the rendered shape (square, circle, diagonal stripe, border) is the
same, only the encoded bytes differ.

Assisted-by: Claude:claude-fable-5-1:Claude Code
This commit is contained in:
Christoph Wille
2026-10-09 09:54:27 +02:00
parent ad2cc62293
commit 3252a4087b
9 changed files with 40 additions and 52 deletions
+28 -32
View File
@@ -29,24 +29,17 @@ using System.Collections.Generic;
using System.IO;
using System.Resources;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
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.PixelFormats;
using SixLabors.ImageSharp.Processing;
using ImageMagick;
using ImageMagick.Drawing;
var dir = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "fixtures");
dir = Path.GetFullPath(dir);
Directory.CreateDirectory(dir);
WriteImage(Path.Combine(dir, "logo.png"), new PngEncoder());
WriteImage(Path.Combine(dir, "logo.bmp"), new BmpEncoder());
WriteImage(Path.Combine(dir, "logo.jpg"), new JpegEncoder { Quality = 92 });
WriteImage(Path.Combine(dir, "logo.gif"), new GifEncoder());
WriteImage(Path.Combine(dir, "logo.png"), MagickFormat.Png);
WriteImage(Path.Combine(dir, "logo.bmp"), MagickFormat.Bmp);
WriteImage(Path.Combine(dir, "logo.jpg"), MagickFormat.Jpeg, quality: 92);
WriteImage(Path.Combine(dir, "logo.gif"), MagickFormat.Gif);
// Multi-frame .ico: 16x16, 32x32, 48x48, each PNG-encoded inside the ICO container so Avalonia
// can decode the largest frame for preview while a savvy viewer can pick any.
@@ -73,29 +66,35 @@ Console.WriteLine($"Wrote fixtures into {dir}");
// --- helpers ---
static void WriteImage(string path, IImageEncoder encoder, int size = 64)
static void WriteImage(string path, MagickFormat format, int size = 64, uint quality = 0)
{
using var img = RenderShape(size);
using var fs = File.Create(path);
img.Save(fs, encoder);
if (quality > 0)
img.Quality = quality;
img.Write(path, format);
}
static Image<Rgba32> RenderShape(int size)
static MagickImage RenderShape(int size)
{
var img = new Image<Rgba32>(size, size, new Rgba32(0xFF, 0xF6, 0xE5));
img.Mutate(ctx => {
var img = new MagickImage(new MagickColor("#FFF6E5"), (uint)size, (uint)size);
float radius = size * 0.35f;
// Drawables is a single fluent command list: stroke/fill settings persist across the
// primitives that follow, so each shape resets the ones it does not want.
new Drawables()
// Filled blue circle in the centre.
ctx.Fill(new Rgba32(0x33, 0x99, 0xCC),
new SixLabors.ImageSharp.Drawing.EllipsePolygon(size / 2f, size / 2f, size * 0.35f));
.FillColor(new MagickColor("#3399CC"))
.StrokeColor(MagickColors.None)
.Ellipse(size / 2.0, size / 2.0, radius, radius, 0, 360)
// Red diagonal stripe.
ctx.DrawLine(new Rgba32(0xCC, 0x33, 0x33),
Math.Max(2, size / 16f),
new PointF(0, size),
new PointF(size, 0));
.FillColor(MagickColors.None)
.StrokeColor(new MagickColor("#CC3333"))
.StrokeWidth(Math.Max(2, size / 16.0))
.Line(0, size, size, 0)
// 1px dark border so the bounds are obvious against any background.
ctx.Draw(new Rgba32(0x33, 0x33, 0x33), 1,
new RectangleF(0, 0, size - 1, size - 1));
});
.StrokeColor(new MagickColor("#333333"))
.StrokeWidth(1)
.Rectangle(0, 0, size - 1, size - 1)
.Draw(img);
return img;
}
@@ -105,13 +104,10 @@ static Image<Rgba32> RenderShape(int size)
static byte[] BuildIcoOrCur(bool isCursor, int[] sizes)
{
var frames = new List<byte[]>(sizes.Length);
var pngEncoder = new PngEncoder();
foreach (var sz in sizes)
{
using var img = RenderShape(sz);
using var ms = new MemoryStream();
img.Save(ms, pngEncoder);
frames.Add(ms.ToArray());
frames.Add(img.ToByteArray(MagickFormat.Png));
}
var fileMs = new MemoryStream();
var w = new BinaryWriter(fileMs);
+4 -5
View File
@@ -5,14 +5,13 @@
<RootNamespace>TestFixtures.Generate</RootNamespace>
<Nullable>enable</Nullable>
<!-- This generator runs once to (re)build the binary fixtures and isn't part of the
solution's CPM scope; pin SkiaSharp inline so the parent Directory.Packages.props
solution's CPM scope; pin the image library inline so the parent Directory.Packages.props
doesn't need to learn about a one-shot tool. -->
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- ImageSharp covers every encoder we need (PNG/JPG/BMP/GIF) cross-platform; SkiaSharp's
encoder list doesn't include BMP/GIF. -->
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.4" />
<!-- Magick.NET (Apache 2.0, bundles the ImageMagick native library for win/linux/osx) covers every
encoder we need (PNG/JPG/BMP/GIF). SkiaSharp's encoder list doesn't include BMP/GIF. -->
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.17.2" />
</ItemGroup>
</Project>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 796 B

After

Width:  |  Height:  |  Size: 556 B

+8 -15
View File
@@ -2,26 +2,19 @@
"version": 1,
"dependencies": {
"net10.0": {
"SixLabors.ImageSharp": {
"Magick.NET-Q8-AnyCPU": {
"type": "Direct",
"requested": "[3.1.11, )",
"resolved": "3.1.11",
"contentHash": "JfPLyigLthuE50yi6tMt7Amrenr/fA31t2CvJyhy/kQmfulIBAqo5T/YFUSRHtuYPXRSaUHygFeh6Qd933EoSw=="
},
"SixLabors.ImageSharp.Drawing": {
"type": "Direct",
"requested": "[2.1.4, )",
"resolved": "2.1.4",
"contentHash": "p4VwtAABggDUhUS5zXldCZHVGfjJl76+SrBHY4biNQ8+890igFK6RL87qIv9GqvEjMcYOar1sPkf2iMQ6uq9/g==",
"requested": "[14.17.2, )",
"resolved": "14.17.2",
"contentHash": "AIvpBH4fvP3/NUwvWsVj9SxAA6hrU4p5/RNfColyrDn4jipl7loR6y4ZgYLXfBQgjmSEc7m2D2DbkWMnZt+brg==",
"dependencies": {
"SixLabors.Fonts": "2.0.4",
"SixLabors.ImageSharp": "3.1.5"
"Magick.NET.Core": "14.17.2"
}
},
"SixLabors.Fonts": {
"Magick.NET.Core": {
"type": "Transitive",
"resolved": "2.0.4",
"contentHash": "4+NKz8W36injp98lmmM07ncp08HAK8c6FZz8vLoKxRPfJeEVWpBHlLYEbQa5rcqKKYqxUv/RVCrb8XcPhfMKUQ=="
"resolved": "14.17.2",
"contentHash": "KKvR6eBUcfgkZpfHfN/UKRcK/FjHW6NXXoRRf5HJdhFk5bYLEEFuKu6op0zfNo6Msah51Yw42p599pz3A0CDQQ=="
}
}
}