Skip to content

Commit 74d1600

Browse files
authored
Bump ImageSharp.Drawing to 1.0.0 (#2099)
* Bump ImageSharp.Drawing to 1.0.0 * Support LineJoin in ImageSharp ImageRenderContext * Fix FontFamily ignored by DrawMultilineText
1 parent 10f2f33 commit 74d1600

7 files changed

Lines changed: 156 additions & 42 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ All notable changes to this project will be documented in this file.
2929
- AxisRendererBase is now generic
3030
- DateTimeAxis.ToDateTime(double value) is now obsolete, replacements are provided (related to #2061)
3131
- Modify some of the examples to make them deterministic
32+
- Update SixLabors.ImageSharp.Drawing to non-beta version 1.0.0
3233

3334
### Removed
3435
- Support for .NET Framework 4.0 and 4.5 (#1839)
@@ -46,6 +47,7 @@ All notable changes to this project will be documented in this file.
4647
- SkiaSharp - Fix use of obsolete functions (#1937)
4748
- Dashed lines are solid when exporting via SkiaSharp.SvgExporter (#1674)
4849
- DateTimeAxis.ToDateTime doesn't behave as intended in .NET 8 (#2061)
50+
- FontFamily ignored by DrawMultilineText
4951

5052
## [2.1.2] - 2022-12-03
5153

@@ -56,6 +58,7 @@ All notable changes to this project will be documented in this file.
5658
- Add `AxisPreference` to `PlotManipulator`
5759
- Add MinimumMajorIntervalCount and MaximumMajorIntervalCount Axis Properties (#24)
5860
- Add VisualStudioToolsManifest.xml to add components to the Visual Studio Designer toolbox (#1446)
61+
- Add support for `LineJoin` in OxyPlot.ImageSharp
5962

6063
### Changed
6164
- Change default `MinimumSegmentLength` to `2` and remove limits for series and annotations with simple geometry (#1853)

Source/OxyPlot.ImageSharp/ImageRenderContext.cs

Lines changed: 133 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// </summary>
88
// --------------------------------------------------------------------------------------------------------------------
99

10+
#nullable enable
11+
1012
namespace OxyPlot.ImageSharp
1113
{
1214
using System;
@@ -158,7 +160,7 @@ public void SaveAsJpeg(Stream output, int quality = 75)
158160
}
159161

160162
/// <inheritdoc/>
161-
public override void DrawText(ScreenPoint p, string text, OxyColor fill, string fontFamily = null, double fontSize = 10, double fontWeight = 400, double rotation = 0, OxyPlot.HorizontalAlignment horizontalAlignment = OxyPlot.HorizontalAlignment.Left, OxyPlot.VerticalAlignment verticalAlignment = OxyPlot.VerticalAlignment.Top, OxySize? maxSize = null)
163+
public override void DrawText(ScreenPoint p, string text, OxyColor fill, string? fontFamily = null, double fontSize = 10, double fontWeight = 400, double rotation = 0, OxyPlot.HorizontalAlignment horizontalAlignment = OxyPlot.HorizontalAlignment.Left, OxyPlot.VerticalAlignment verticalAlignment = OxyPlot.VerticalAlignment.Top, OxySize? maxSize = null)
162164
{
163165
if (text == null || !fill.IsVisible())
164166
{
@@ -176,18 +178,18 @@ public override void DrawText(ScreenPoint p, string text, OxyColor fill, string
176178
var sin = (float)Math.Sin(rotation * Math.PI / 180.0);
177179

178180
// measure bounds of the whole text (we only need the height)
179-
var bounds = this.MeasureTextLoose(text, fontFamily, fontSize, fontWeight);
181+
var bounds = this.MeasureTextLoose(text, fontFamily!, fontSize, fontWeight);
180182
var boundsHeight = this.Convert(bounds.Height);
181183
var offsetHeight = new PointF(boundsHeight * -sin, boundsHeight * cos);
182184

183185
// determine the font metrids for this font size at 96 DPI
184-
var actualDescent = this.Convert(actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.Descender));
186+
var actualDescent = this.Convert(actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.VerticalMetrics.Descender));
185187
var offsetDescent = new PointF(actualDescent * -sin, actualDescent * cos);
186188

187-
var actualLineHeight = this.Convert(actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.LineHeight));
189+
var actualLineHeight = this.Convert(actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.VerticalMetrics.LineHeight));
188190
var offsetLineHeight = new PointF(actualLineHeight * -sin, actualLineHeight * cos);
189191

190-
var actualLineGap = this.Convert(actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.LineGap));
192+
var actualLineGap = this.Convert(actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.VerticalMetrics.LineGap));
191193
var offsetLineGap = new PointF(actualLineGap * -sin, actualLineGap * cos);
192194

193195
// find top of the whole text
@@ -222,7 +224,7 @@ public override void DrawText(ScreenPoint p, string text, OxyColor fill, string
222224
}
223225

224226
// measure bounds of just the line (we only need the width)
225-
var lineBounds = this.MeasureTextLoose(line, fontFamily, fontSize, fontWeight);
227+
var lineBounds = this.MeasureTextLoose(line, fontFamily!, fontSize, fontWeight);
226228
var lineBoundsWidth = this.Convert(lineBounds.Width);
227229
var offsetLineWidth = new PointF(lineBoundsWidth * cos, lineBoundsWidth * sin);
228230

@@ -254,9 +256,9 @@ public override void DrawText(ScreenPoint p, string text, OxyColor fill, string
254256
}
255257

256258
/// <inheritdoc/>
257-
public override OxySize MeasureText(string text, string fontFamily = null, double fontSize = 10, double fontWeight = 500)
259+
public override OxySize MeasureText(string text, string? fontFamily = null, double fontSize = 10, double fontWeight = 500)
258260
{
259-
return this.MeasureTextLoose(text, fontFamily, fontSize, fontWeight);
261+
return this.MeasureTextLoose(text, fontFamily!, fontSize, fontWeight);
260262
}
261263

262264
/// <inheritdoc/>
@@ -367,53 +369,70 @@ public override void DrawImage(
367369
/// <inheritdoc/>
368370
public override void DrawLine(IList<ScreenPoint> points, OxyColor stroke, double thickness, EdgeRenderingMode edgeRenderingMode, double[] dashArray, LineJoin lineJoin)
369371
{
370-
if (points.Count < 2 || !stroke.IsVisible() || thickness <= 0)
372+
if (points.Count < 2)
371373
{
372374
return;
373375
}
374376

375-
var actualThickness = this.GetActualThickness(thickness, edgeRenderingMode);
376-
var actualDashArray = dashArray != null
377-
? this.ConvertDashArray(dashArray, actualThickness)
378-
: null;
377+
var pen = this.GetPen(stroke, thickness, dashArray, edgeRenderingMode, lineJoin);
378+
379+
if (pen is null)
380+
{
381+
return;
382+
}
379383

380-
var pen = actualDashArray != null
381-
? new Pen(ToRgba32(stroke), actualThickness, actualDashArray)
382-
: new Pen(ToRgba32(stroke), actualThickness);
383384
var actualPoints = this.GetActualPoints(points, thickness, edgeRenderingMode).ToArray();
384385
var options = this.CreateDrawingOptions(this.ShouldUseAntiAliasingForLine(edgeRenderingMode, points));
385386

387+
if (this.PointsAreAllTheSame(actualPoints))
388+
{
389+
// return early if all the points are the same, as this causes a crash in ImageSharp
390+
return;
391+
}
392+
386393
this.Target.Mutate(img =>
387394
{
388-
img.DrawLines(options, pen, actualPoints);
395+
img.DrawLine(options, pen, actualPoints);
389396
});
390397
}
391398

399+
/// <summary>
400+
/// Determines whether all the points in the given collection are the same.
401+
/// </summary>
402+
/// <param name="points">The collection of points to compare.</param>
403+
/// <returns><code>true</code> if all the points compare equal, otherwise <code>false</code>.</returns>
404+
private bool PointsAreAllTheSame(IList<PointF> points)
405+
{
406+
for (int i = 1; i < points.Count; i++)
407+
{
408+
if (points[i] != points[0])
409+
{
410+
return false;
411+
}
412+
}
413+
414+
return true;
415+
}
416+
392417
/// <inheritdoc/>
393-
public override void DrawPolygon(IList<ScreenPoint> points, OxyColor fill, OxyColor stroke, double thickness, EdgeRenderingMode edgeRenderingMode, double[] dashArray, LineJoin lineJoin)
418+
public override void DrawPolygon(IList<ScreenPoint> points, OxyColor fill, OxyColor stroke, double thickness, EdgeRenderingMode edgeRenderingMode, double[]? dashArray, LineJoin lineJoin)
394419
{
395-
var fillInvisible = !fill.IsVisible();
396-
var strokeInvisible = !stroke.IsVisible() || thickness <= 0;
420+
if (points.Count < 2)
421+
{
422+
return;
423+
}
424+
425+
var pen = this.GetPen(stroke, thickness, dashArray, edgeRenderingMode, lineJoin);
426+
var brush = this.GetBrush(fill);
397427

398-
if ((fillInvisible && strokeInvisible) || points.Count < 2)
428+
if (pen is null && brush is null)
399429
{
400430
return;
401431
}
402432

403-
var actualThickness = this.GetActualThickness(thickness, edgeRenderingMode);
404-
var actualDashArray = dashArray != null
405-
? this.ConvertDashArray(dashArray, actualThickness)
406-
: null;
407-
408-
var pen = strokeInvisible ? null :
409-
actualDashArray != null
410-
? new Pen(ToRgba32(stroke), actualThickness, actualDashArray)
411-
: new Pen(ToRgba32(stroke), actualThickness);
412433
var actualPoints = this.GetActualPoints(points, thickness, edgeRenderingMode).ToArray();
413434
var options = this.CreateDrawingOptions(this.ShouldUseAntiAliasingForLine(edgeRenderingMode, points));
414435

415-
var brush = fillInvisible ? null : Brushes.Solid(ToRgba32(fill));
416-
417436
this.Target.Mutate(img =>
418437
{
419438
if (brush != null)
@@ -428,6 +447,83 @@ public override void DrawPolygon(IList<ScreenPoint> points, OxyColor fill, OxyCo
428447
});
429448
}
430449

450+
/// <summary>
451+
/// Gets a <see cref="Brush"/>.
452+
/// </summary>
453+
/// <param name="fill">The fill color.</param>
454+
/// <returns>A <see cref="Brush"/>, or <code>null</code> if the fill would be invisible.</returns>
455+
private Brush? GetBrush(OxyColor fill)
456+
{
457+
if (!fill.IsVisible())
458+
{
459+
return null;
460+
}
461+
462+
return Brushes.Solid(ToRgba32(fill));
463+
}
464+
465+
/// <summary>
466+
/// Gets a <see cref="Pen"/>.
467+
/// </summary>
468+
/// <param name="stroke">The stroke color.</param>
469+
/// <param name="thickness">The stroke thickness (in device independent units, 1/96 inch).</param>
470+
/// <param name="edgeRenderingMode">The edge rendering mode.</param>
471+
/// <param name="dashArray">The dash array (in device independent units, 1/96 inch). Use <c>null</c> to get a solid line.</param>
472+
/// <param name="lineJoin">The line join type.</param>
473+
/// <returns>A <see cref="Pen"/>, or <code>null</code> if the stroke would be invisible.</returns>
474+
private Pen? GetPen(OxyColor stroke, double thickness, double[]? dashArray, EdgeRenderingMode edgeRenderingMode, LineJoin lineJoin)
475+
{
476+
if (this.IsStrokeInvisible(stroke, thickness))
477+
{
478+
return null;
479+
}
480+
481+
var actualThickness = this.GetActualThickness(thickness, edgeRenderingMode);
482+
var actualDashArray = dashArray is null ? null : this.ConvertDashArray(dashArray, actualThickness);
483+
var actualJointStyle = this.GetLineJointStyle(lineJoin);
484+
485+
var penOptions = new PenOptions(ToRgba32(stroke), actualThickness, actualDashArray)
486+
{
487+
JointStyle = actualJointStyle,
488+
};
489+
490+
return new PatternPen(penOptions);
491+
}
492+
493+
/// <summary>
494+
/// Converts a <see cref="LineJoin" /> to a <see cref="JointStyle"/>.
495+
/// </summary>
496+
/// <param name="lineJoin">The line join type.</param>
497+
/// <returns>The converted line join style.</returns>
498+
protected JointStyle GetLineJointStyle(LineJoin lineJoin)
499+
{
500+
switch (lineJoin)
501+
{
502+
case LineJoin.Miter:
503+
return JointStyle.Miter;
504+
505+
case LineJoin.Bevel:
506+
return JointStyle.Square;
507+
508+
case LineJoin.Round:
509+
return JointStyle.Round;
510+
511+
default:
512+
return JointStyle.Miter;
513+
}
514+
}
515+
516+
/// <summary>
517+
/// Determines whether the given color and thickness would be invisible.
518+
/// </summary>
519+
/// <param name="stroke">The stroke color.</param>
520+
/// <param name="thickness">The stroke thickness (in device independent units, 1/96 inch).</param>
521+
/// <returns>True if the stroke would be invisible; otherwise false</returns>
522+
protected bool IsStrokeInvisible(OxyColor stroke, double thickness)
523+
{
524+
return !stroke.IsVisible() || thickness <= 0;
525+
}
526+
431527
/// <inheritdoc/>
432528
protected override void ResetClip()
433529
{
@@ -548,14 +644,14 @@ private void Blit(Image<Rgba32> source, Image<Rgba32> destination, Rectangle rec
548644
}
549645
}
550646

551-
private Font GetFontOrThrow(string fontFamily, double fontSize, FontStyle fontWeight, bool allowFallback = true)
647+
private Font GetFontOrThrow(string? fontFamily, double fontSize, FontStyle fontWeight, bool allowFallback = true)
552648
{
553649
var family = this.GetFamilyOrFallbackOrThrow(fontFamily, allowFallback);
554650
var actualFontSize = this.NominalFontSizeToPoints(fontSize);
555651
return new Font(family, (float)actualFontSize, fontWeight);
556652
}
557653

558-
private FontFamily GetFamilyOrFallbackOrThrow(string fontFamily = null, bool allowFallback = true)
654+
private FontFamily GetFamilyOrFallbackOrThrow(string? fontFamily = null, bool allowFallback = true)
559655
{
560656
if (fontFamily == null)
561657
{
@@ -606,8 +702,8 @@ private OxySize MeasureTextLoose(string text, string fontFamily, double fontSize
606702
var tight = this.MeasureTextTight(text, fontFamily, fontSize, fontWeight);
607703
var width = tight.Width;
608704

609-
var lineHeight = actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.LineHeight);
610-
var lineGap = actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.LineGap);
705+
var lineHeight = actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.VerticalMetrics.LineHeight);
706+
var lineGap = actualFontSize * this.MilliPointsToNominalResolution(font.FontMetrics.VerticalMetrics.LineGap);
611707
var lineCount = CountLines(text);
612708

613709
var height = (lineHeight * lineCount) + (lineGap * (lineCount - 1));
@@ -630,7 +726,7 @@ private OxySize MeasureTextTight(string text, string fontFamily, double fontSize
630726
var font = this.GetFontOrThrow(fontFamily, fontSize, this.ToFontStyle(fontWeight));
631727
var actualFontSize = this.NominalFontSizeToPoints(fontSize);
632728

633-
var result = TextMeasurer.Measure(text, new TextOptions(font) { Dpi = this.Dpi });
729+
var result = TextMeasurer.MeasureSize(text, new TextOptions(font) { Dpi = this.Dpi });
634730
return new OxySize(this.ConvertBack(result.Width), this.ConvertBack(result.Height));
635731
}
636732

Source/OxyPlot.ImageSharp/JpegExporter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// </summary>
88
// --------------------------------------------------------------------------------------------------------------------
99

10+
#nullable enable
11+
1012
namespace OxyPlot.ImageSharp
1113
{
1214
using System.IO;

Source/OxyPlot.ImageSharp/MirrorPadProcessor.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1-
namespace OxyPlot.ImageSharp
1+
// --------------------------------------------------------------------------------------------------------------------
2+
// <copyright file="MirrorPadProcessor.cs" company="OxyPlot">
3+
// Copyright (c) 2014 OxyPlot contributors
4+
// </copyright>
5+
// <summary>
6+
// Provides an an ImageProcessor that performs a single-pizel mirror/clamp along the edge of an image.
7+
// </summary>
8+
// --------------------------------------------------------------------------------------------------------------------
9+
10+
#nullable enable
11+
12+
namespace OxyPlot.ImageSharp
213
{
314
using SixLabors.ImageSharp;
415
using SixLabors.ImageSharp.PixelFormats;
516
using SixLabors.ImageSharp.Processing.Processors;
617

718
/// <summary>
8-
/// Performs a 'mirror' (clamp) along the edge of the image, which is used to assist with drawing non-pixel aligned and interpolate images.
19+
/// Performs a single-pizel mirror/clamp along the edge of the image, which is used to assist with drawing non-pixel aligned and interpolate images.
920
/// </summary>
1021
internal class MirrorPadProcessor : IImageProcessor
1122
{

Source/OxyPlot.ImageSharp/OxyPlot.ImageSharp.csproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<LangVersion>8</LangVersion>
3+
<LangVersion>9</LangVersion>
44
<TargetFrameworks>netstandard2.0</TargetFrameworks>
55
<Description>OxyPlot is a plotting library for .NET. This is package is based on SixLabors.ImageSharp.</Description>
66
<PackageTags>plotting plot charting chart imagesharp</PackageTags>
77
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
88
<GenerateDocumentationFile>True</GenerateDocumentationFile>
99
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
10-
<NoWarn>$(NoWarn);NU5104</NoWarn>
1110
<WarningsAsErrors />
1211
</PropertyGroup>
1312
<ItemGroup>
1413
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.9" />
15-
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta15" />
14+
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
1615
</ItemGroup>
1716
<ItemGroup>
1817
<ProjectReference Include="..\OxyPlot\OxyPlot.csproj" />

Source/OxyPlot.ImageSharp/PngExporter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// </summary>
88
// --------------------------------------------------------------------------------------------------------------------
99

10+
#nullable enable
11+
1012
namespace OxyPlot.ImageSharp
1113
{
1214
using System.IO;

Source/OxyPlot/Rendering/RenderContext/RenderingExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ public static void DrawMultilineText(this IRenderContext rc, ScreenPoint point,
207207
new ScreenPoint(point.X, point.Y + (i * dy)),
208208
lines[i],
209209
color,
210+
fontFamily: fontFamily,
210211
fontWeight: fontWeight,
211212
fontSize: fontSize);
212213
}

0 commit comments

Comments
 (0)