Skip to content

Commit

Permalink
Replace System.Drawing.Common with SkiaSharp (#455)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zombach authored Jul 13, 2024
1 parent 50829ea commit 327af3d
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 76 deletions.
33 changes: 17 additions & 16 deletions Algorithms.Tests/Other/FloodFillTest.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
using System;
using System.Drawing;
using FluentAssertions;
using NUnit.Framework;
using SkiaSharp;
using System;

namespace Algorithms.Tests.Other;

public static class Tests
{
private static readonly Color Black = Color.FromArgb(255, 0, 0, 0);
private static readonly Color Green = Color.FromArgb(255, 0, 255, 0);
private static readonly Color Violet = Color.FromArgb(255, 255, 0, 255);
private static readonly Color White = Color.FromArgb(255, 255, 255, 255);
private static readonly Color Orange = Color.FromArgb(255, 255, 128, 0);
private const byte Alpha = 255;
private static readonly SKColor Black = new(0, 0, 0, Alpha);
private static readonly SKColor Green = new(0, 255, 0, Alpha);
private static readonly SKColor Violet = new(255, 0, 255, Alpha);
private static readonly SKColor White = new(255, 255, 255, Alpha);
private static readonly SKColor Orange = new(255, 128, 0, Alpha);

[Test]
public static void BreadthFirstSearch_ThrowsArgumentOutOfRangeException()
Expand Down Expand Up @@ -63,9 +64,9 @@ public static void DepthFirstSearch_Test3()
TestAlgorithm(Algorithms.Other.FloodFill.DepthFirstSearch, (1, 1), Green, Orange, (6, 4), White);
}

private static Bitmap GenerateTestBitmap()
private static SKBitmap GenerateTestBitmap()
{
Color[,] layout =
SKColor[,] layout =
{
{Violet, Violet, Green, Green, Black, Green, Green},
{Violet, Green, Green, Black, Green, Green, Green},
Expand All @@ -76,7 +77,7 @@ private static Bitmap GenerateTestBitmap()
{Violet, Violet, Violet, Violet, Violet, Violet, Violet},
};

Bitmap bitmap = new(7, 7);
SKBitmap bitmap = new(7, 7);
for (int x = 0; x < layout.GetLength(0); x++)
{
for (int y = 0; y < layout.GetLength(1); y++)
Expand All @@ -89,16 +90,16 @@ private static Bitmap GenerateTestBitmap()
}

private static void TestAlgorithm(
Action<Bitmap, ValueTuple<int, int>, Color, Color> algorithm,
Action<SKBitmap, ValueTuple<int, int>, SKColor, SKColor> algorithm,
ValueTuple<int, int> fillLocation,
Color targetColor,
Color replacementColor,
SKColor targetColor,
SKColor replacementColor,
ValueTuple<int, int> testLocation,
Color expectedColor)
SKColor expectedColor)
{
Bitmap bitmap = GenerateTestBitmap();
SKBitmap bitmap = GenerateTestBitmap();
algorithm(bitmap, fillLocation, targetColor, replacementColor);
Color actualColor = bitmap.GetPixel(testLocation.Item1, testLocation.Item2);
SKColor actualColor = bitmap.GetPixel(testLocation.Item1, testLocation.Item2);
actualColor.Should().Be(expectedColor);
}
}
15 changes: 7 additions & 8 deletions Algorithms.Tests/Other/KochSnowflakeTest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Numerics;
using Algorithms.Other;
using FluentAssertions;
using NUnit.Framework;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Numerics;

namespace Algorithms.Tests.Other;

Expand Down Expand Up @@ -39,13 +39,12 @@ public static void TestKochSnowflakeExample()
var bitmapWidth = 600;
var offsetX = bitmapWidth / 10f;
var offsetY = bitmapWidth / 3.7f;

Bitmap bitmap = KochSnowflake.GetKochSnowflake();
SKBitmap bitmap = KochSnowflake.GetKochSnowflake();
bitmap.GetPixel(0, 0)
.Should()
.Be(Color.FromArgb(255, 255, 255, 255), "because the background should be white");
.Be(new SKColor(255, 255, 255, 255), "because the background should be white");
bitmap.GetPixel((int)offsetX, (int)offsetY)
.Should()
.Be(Color.FromArgb(255, 0, 0, 0), "because the snowflake is drawn in black and this is the position of the first vector");
.Be(new SKColor(0, 0, 0, 255), "because the snowflake is drawn in black and this is the position of the first vector");
}
}
14 changes: 7 additions & 7 deletions Algorithms.Tests/Other/MandelbrotTest.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Drawing;
using Algorithms.Other;
using NUnit.Framework;
using SkiaSharp;

namespace Algorithms.Tests.Other;

Expand All @@ -28,22 +28,22 @@ public static void MaxStepIsZeroOrNegative_ThrowsArgumentOutOfRangeException()
[Test]
public static void TestBlackAndWhite()
{
Bitmap bitmap = Mandelbrot.GetBitmap(useDistanceColorCoding: false);
SKBitmap bitmap = Mandelbrot.GetBitmap(useDistanceColorCoding: false);
// Pixel outside the Mandelbrot set should be white.
Assert.That(Color.FromArgb(255, 255, 255, 255), Is.EqualTo(bitmap.GetPixel(0, 0)));
Assert.That(new SKColor(255, 255, 255, 255), Is.EqualTo(bitmap.GetPixel(0, 0)));

// Pixel inside the Mandelbrot set should be black.
Assert.That(Color.FromArgb(255, 0, 0, 0), Is.EqualTo(bitmap.GetPixel(400, 300)));
Assert.That(new SKColor(0, 0, 0, 255), Is.EqualTo(bitmap.GetPixel(400, 300)));
}

[Test]
public static void TestColorCoded()
{
Bitmap bitmap = Mandelbrot.GetBitmap(useDistanceColorCoding: true);
SKBitmap bitmap = Mandelbrot.GetBitmap(useDistanceColorCoding: true);
// Pixel distant to the Mandelbrot set should be red.
Assert.That(Color.FromArgb(255, 255, 0, 0), Is.EqualTo(bitmap.GetPixel(0, 0)));
Assert.That(new SKColor(255, 0, 0, 255), Is.EqualTo(bitmap.GetPixel(0, 0)));

// Pixel inside the Mandelbrot set should be black.
Assert.That(Color.FromArgb(255, 0, 0, 0), Is.EqualTo(bitmap.GetPixel(400, 300)));
Assert.That(new SKColor(0, 0, 0, 255), Is.EqualTo(bitmap.GetPixel(400, 300)));
}
}
3 changes: 2 additions & 1 deletion Algorithms/Algorithms.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.8" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.8" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Drawing.Common" Version="5.0.3" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 5 additions & 5 deletions Algorithms/Other/FloodFill.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using SkiaSharp;

namespace Algorithms.Other;

Expand All @@ -23,7 +23,7 @@ public static class FloodFill
/// <param name="location">The start location on the bitmap.</param>
/// <param name="targetColor">The old color to be replaced.</param>
/// <param name="replacementColor">The new color to replace the old one.</param>
public static void BreadthFirstSearch(Bitmap bitmap, (int x, int y) location, Color targetColor, Color replacementColor)
public static void BreadthFirstSearch(SKBitmap bitmap, (int x, int y) location, SKColor targetColor, SKColor replacementColor)
{
if (location.x < 0 || location.x >= bitmap.Width || location.y < 0 || location.y >= bitmap.Height)
{
Expand All @@ -46,7 +46,7 @@ public static void BreadthFirstSearch(Bitmap bitmap, (int x, int y) location, Co
/// <param name="location">The start location on the bitmap.</param>
/// <param name="targetColor">The old color to be replaced.</param>
/// <param name="replacementColor">The new color to replace the old one.</param>
public static void DepthFirstSearch(Bitmap bitmap, (int x, int y) location, Color targetColor, Color replacementColor)
public static void DepthFirstSearch(SKBitmap bitmap, (int x, int y) location, SKColor targetColor, SKColor replacementColor)
{
if (location.x < 0 || location.x >= bitmap.Width || location.y < 0 || location.y >= bitmap.Height)
{
Expand All @@ -56,7 +56,7 @@ public static void DepthFirstSearch(Bitmap bitmap, (int x, int y) location, Colo
DepthFirstFill(bitmap, location, targetColor, replacementColor);
}

private static void BreadthFirstFill(Bitmap bitmap, (int x, int y) location, Color targetColor, Color replacementColor, List<(int x, int y)> queue)
private static void BreadthFirstFill(SKBitmap bitmap, (int x, int y) location, SKColor targetColor, SKColor replacementColor, List<(int x, int y)> queue)
{
(int x, int y) currentLocation = queue[0];
queue.RemoveAt(0);
Expand All @@ -77,7 +77,7 @@ private static void BreadthFirstFill(Bitmap bitmap, (int x, int y) location, Col
}
}

private static void DepthFirstFill(Bitmap bitmap, (int x, int y) location, Color targetColor, Color replacementColor)
private static void DepthFirstFill(SKBitmap bitmap, (int x, int y) location, SKColor targetColor, SKColor replacementColor)
{
if (bitmap.GetPixel(location.x, location.y) == targetColor)
{
Expand Down
47 changes: 26 additions & 21 deletions Algorithms/Other/KochSnowflake.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Numerics;
using SkiaSharp;

namespace Algorithms.Other;

Expand Down Expand Up @@ -52,7 +52,7 @@ public static List<Vector2> Iterate(List<Vector2> initialVectors, int steps = 5)
/// <param name="bitmapWidth">The width of the rendered bitmap.</param>
/// <param name="steps">The number of iterations.</param>
/// <returns>The bitmap of the rendered Koch snowflake.</returns>
public static Bitmap GetKochSnowflake(
public static SKBitmap GetKochSnowflake(
int bitmapWidth = 600,
int steps = 5)
{
Expand Down Expand Up @@ -124,31 +124,36 @@ private static Vector2 Rotate(Vector2 vector, float angleInDegrees)
/// <param name="bitmapWidth">The width of the rendered bitmap.</param>
/// <param name="bitmapHeight">The height of the rendered bitmap.</param>
/// <returns>The bitmap of the rendered edges.</returns>
private static Bitmap GetBitmap(
private static SKBitmap GetBitmap(
List<Vector2> vectors,
int bitmapWidth,
int bitmapHeight)
{
Bitmap bitmap = new(bitmapWidth, bitmapHeight);
SKBitmap bitmap = new(bitmapWidth, bitmapHeight);
var canvas = new SKCanvas(bitmap);

using (Graphics graphics = Graphics.FromImage(bitmap))
// Set the background white
var rect = SKRect.Create(0, 0, bitmapWidth, bitmapHeight);

var paint = new SKPaint
{
// Set the background white
var imageSize = new Rectangle(0, 0, bitmapWidth, bitmapHeight);
graphics.FillRectangle(Brushes.White, imageSize);

// Draw the edges
for (var i = 0; i < vectors.Count - 1; i++)
{
Pen blackPen = new(Color.Black, 1);

var x1 = vectors[i].X;
var y1 = vectors[i].Y;
var x2 = vectors[i + 1].X;
var y2 = vectors[i + 1].Y;

graphics.DrawLine(blackPen, x1, y1, x2, y2);
}
Style = SKPaintStyle.Fill,
Color = SKColors.White,
};

canvas.DrawRect(rect, paint);

paint.Color = SKColors.Black;

// Draw the edges
for (var i = 0; i < vectors.Count - 1; i++)
{
var x1 = vectors[i].X;
var y1 = vectors[i].Y;
var x2 = vectors[i + 1].X;
var y2 = vectors[i + 1].Y;

canvas.DrawLine(new SKPoint(x1, y1), new SKPoint(x2, y2), paint);
}

return bitmap;
Expand Down
38 changes: 20 additions & 18 deletions Algorithms/Other/Mandelbrot.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Drawing;
using SkiaSharp;

namespace Algorithms.Other;

Expand All @@ -22,6 +22,8 @@ namespace Algorithms.Other;
/// </summary>
public static class Mandelbrot
{
private const byte Alpha = 255;

/// <summary>
/// Method to generate the bitmap of the Mandelbrot set. Two types of coordinates
/// are used: bitmap-coordinates that refer to the pixels and figure-coordinates
Expand All @@ -39,7 +41,7 @@ public static class Mandelbrot
/// <param name="maxStep">Maximum number of steps to check for divergent behavior.</param>
/// <param name="useDistanceColorCoding">Render in color or black and white.</param>
/// <returns>The bitmap of the rendered Mandelbrot set.</returns>
public static Bitmap GetBitmap(
public static SKBitmap GetBitmap(
int bitmapWidth = 800,
int bitmapHeight = 600,
double figureCenterX = -0.6,
Expand Down Expand Up @@ -69,7 +71,7 @@ public static Bitmap GetBitmap(
$"{nameof(maxStep)} should be greater than zero");
}

var bitmap = new Bitmap(bitmapWidth, bitmapHeight);
var bitmap = new SKBitmap(bitmapWidth, bitmapHeight);
var figureHeight = figureWidth / bitmapWidth * bitmapHeight;

// loop through the bitmap-coordinates
Expand Down Expand Up @@ -100,22 +102,22 @@ public static Bitmap GetBitmap(
/// </summary>
/// <param name="distance">Distance until divergence threshold.</param>
/// <returns>The color corresponding to the distance.</returns>
private static Color BlackAndWhiteColorMap(double distance) =>
private static SKColor BlackAndWhiteColorMap(double distance) =>
distance >= 1
? Color.FromArgb(255, 0, 0, 0)
: Color.FromArgb(255, 255, 255, 255);
? new SKColor(0, 0, 0, Alpha)
: new SKColor(255, 255, 255, Alpha);

/// <summary>
/// Color-coding taking the relative distance into account. The Mandelbrot set
/// is black.
/// </summary>
/// <param name="distance">Distance until divergence threshold.</param>
/// <returns>The color corresponding to the distance.</returns>
private static Color ColorCodedColorMap(double distance)
private static SKColor ColorCodedColorMap(double distance)
{
if (distance >= 1)
{
return Color.FromArgb(255, 0, 0, 0);
return new SKColor(0, 0, 0, Alpha);
}

// simplified transformation of HSV to RGB
Expand All @@ -126,19 +128,19 @@ private static Color ColorCodedColorMap(double distance)
var hi = (int)Math.Floor(hue / 60) % 6;
var f = hue / 60 - Math.Floor(hue / 60);

var v = (int)val;
var p = 0;
var q = (int)(val * (1 - f * saturation));
var t = (int)(val * (1 - (1 - f) * saturation));
var v = (byte)val;
const byte p = 0;
var q = (byte)(val * (1 - f * saturation));
var t = (byte)(val * (1 - (1 - f) * saturation));

switch (hi)
{
case 0: return Color.FromArgb(255, v, t, p);
case 1: return Color.FromArgb(255, q, v, p);
case 2: return Color.FromArgb(255, p, v, t);
case 3: return Color.FromArgb(255, p, q, v);
case 4: return Color.FromArgb(255, t, p, v);
default: return Color.FromArgb(255, v, p, q);
case 0: return new SKColor(v, t, p, Alpha);
case 1: return new SKColor(q, v, p, Alpha);
case 2: return new SKColor(p, v, t, Alpha);
case 3: return new SKColor(p, q, v, Alpha);
case 4: return new SKColor(t, p, v, Alpha);
default: return new SKColor(v, p, q, Alpha);
}
}

Expand Down

0 comments on commit 327af3d

Please sign in to comment.