Skip to content

Commit 995d9f3

Browse files
committed
Fixed a scaling issue caused by incorrectly overwritten ViewBox. (#21)
Improved performance of 'Surface.IsEmpty()' extension method. Minor refactoring.
1 parent 0779e4a commit 995d9f3

File tree

9 files changed

+194
-108
lines changed

9 files changed

+194
-108
lines changed

SvgFileType/Extensions/DocumentExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ namespace SvgFileTypePlugin.Extensions;
88

99
internal static class DocumentExtensions
1010
{
11-
public static void SetDpi(this Document doc, double x, double y)
11+
public static void SetDpi(this Document doc, double x, double y, MeasurementUnit dpuUnit = MeasurementUnit.Inch)
1212
{
1313
ArgumentNullException.ThrowIfNull(doc);
1414

15-
doc.DpuUnit = MeasurementUnit.Inch;
15+
doc.DpuUnit = dpuUnit;
1616
doc.DpuX = x;
1717
doc.DpuY = y;
1818
}
1919

20-
public static void SetDpi(this Document doc, double dpi)
20+
public static void SetDpi(this Document doc, double dpi, MeasurementUnit dpuUnit = MeasurementUnit.Inch)
2121
{
22-
SetDpi(doc, dpi, dpi);
22+
SetDpi(doc, dpi, dpi, dpuUnit);
2323
}
2424
}

SvgFileType/Extensions/SurfaceExtensions.cs

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// Use of this source code is governed by GNU General Public License (GPL-2.0) that can be found in the COPYING file.
33

44
using System;
5-
using System.Runtime.CompilerServices;
5+
using System.Runtime.Intrinsics;
6+
using System.Runtime.Intrinsics.X86;
67
using PaintDotNet;
78

89
namespace SvgFileTypePlugin.Extensions;
@@ -28,20 +29,116 @@ public static Document CreateSingleLayerDocument(this Surface surface, bool take
2829
return document;
2930
}
3031

31-
public static bool IsEmpty(this Surface surface)
32+
public static unsafe bool IsEmpty(this Surface surface)
3233
{
3334
ArgumentNullException.ThrowIfNull(surface);
3435

35-
for (int y = 0; y < surface.Height; y++)
36+
const int unrollFactor = 4;
37+
int pixcnt = surface.Width * surface.Height;
38+
sbyte* ptr = (sbyte*)surface.Scan0.VoidStar;
39+
40+
if (Avx2.IsSupported && pixcnt >= Vector256<uint>.Count)
3641
{
37-
ref ColorBgra pix = ref surface.GetRowReferenceUnchecked(y);
38-
for (int x = surface.Width; x > 0; x--)
42+
Vector256<sbyte> valphamask = Vector256.Create(
43+
3, 7, 11, 15, 19, 23, 27, 31,
44+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
45+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
46+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff).AsSByte();
47+
Vector256<sbyte> zero = Vector256<sbyte>.Zero;
48+
while (pixcnt >= Vector256<uint>.Count * unrollFactor)
49+
{
50+
Vector256<sbyte> v0 = Avx.LoadVector256(ptr);
51+
Vector256<sbyte> v1 = Avx.LoadVector256(ptr + Vector256<sbyte>.Count);
52+
Vector256<sbyte> v2 = Avx.LoadVector256(ptr + 2 * Vector256<sbyte>.Count);
53+
Vector256<sbyte> v3 = Avx.LoadVector256(ptr + 3 * Vector256<sbyte>.Count);
54+
55+
v0 = Avx2.Shuffle(v0, valphamask);
56+
v1 = Avx2.Shuffle(v1, valphamask);
57+
v2 = Avx2.Shuffle(v2, valphamask);
58+
v3 = Avx2.Shuffle(v3, valphamask);
59+
60+
v0 = Avx2.CompareGreaterThan(v0, zero);
61+
v1 = Avx2.CompareGreaterThan(v1, zero);
62+
v2 = Avx2.CompareGreaterThan(v2, zero);
63+
v3 = Avx2.CompareGreaterThan(v3, zero);
64+
65+
if (Avx2.MoveMask(v0) != 0 || Avx2.MoveMask(v1) != 0
66+
|| Avx2.MoveMask(v2) != 0 || Avx2.MoveMask(v3) != 0)
67+
{
68+
return false;
69+
}
70+
71+
ptr += Vector256<sbyte>.Count * unrollFactor;
72+
pixcnt -= Vector256<uint>.Count * unrollFactor;
73+
}
74+
75+
while (pixcnt >= Vector256<sbyte>.Count)
3976
{
40-
if (pix.A > 0)
77+
Vector256<sbyte> v = Avx.LoadVector256(ptr);
78+
v = Avx2.Shuffle(v, valphamask);
79+
v = Avx2.CompareGreaterThan(v, zero);
80+
if (Avx2.MoveMask(v) != 0)
4181
return false;
42-
pix = ref Unsafe.Add(ref pix, 1);
82+
ptr += Vector256<sbyte>.Count;
83+
pixcnt -= Vector256<uint>.Count;
4384
}
4485
}
86+
87+
if (Ssse3.IsSupported && pixcnt >= Vector128<uint>.Count)
88+
{
89+
Vector128<sbyte> valphamask = Vector128.Create(
90+
3, 7, 11, 15,
91+
0xff, 0xff, 0xff, 0xff,
92+
0xff, 0xff, 0xff, 0xff,
93+
0xff, 0xff, 0xff, 0xff).AsSByte();
94+
Vector128<sbyte> zero = Vector128<sbyte>.Zero;
95+
while (pixcnt >= Vector128<uint>.Count * unrollFactor)
96+
{
97+
Vector128<sbyte> v0 = Sse2.LoadVector128(ptr);
98+
Vector128<sbyte> v1 = Sse2.LoadVector128(ptr + Vector128<sbyte>.Count);
99+
Vector128<sbyte> v2 = Sse2.LoadVector128(ptr + 2 * Vector128<sbyte>.Count);
100+
Vector128<sbyte> v3 = Sse2.LoadVector128(ptr + 3 * Vector128<sbyte>.Count);
101+
102+
v0 = Ssse3.Shuffle(v0, valphamask);
103+
v1 = Ssse3.Shuffle(v1, valphamask);
104+
v2 = Ssse3.Shuffle(v2, valphamask);
105+
v3 = Ssse3.Shuffle(v3, valphamask);
106+
107+
v0 = Sse2.CompareGreaterThan(v0, zero);
108+
v1 = Sse2.CompareGreaterThan(v1, zero);
109+
v2 = Sse2.CompareGreaterThan(v2, zero);
110+
v3 = Sse2.CompareGreaterThan(v3, zero);
111+
112+
if (Sse2.MoveMask(v0) != 0 || Sse2.MoveMask(v1) != 0
113+
|| Sse2.MoveMask(v2) != 0 || Sse2.MoveMask(v3) != 0)
114+
{
115+
return false;
116+
}
117+
118+
ptr += Vector128<sbyte>.Count * unrollFactor;
119+
pixcnt -= Vector128<uint>.Count * unrollFactor;
120+
}
121+
122+
while (pixcnt >= Vector128<sbyte>.Count)
123+
{
124+
Vector128<sbyte> v = Sse2.LoadVector128(ptr);
125+
v = Ssse3.Shuffle(v, valphamask);
126+
v = Sse2.CompareGreaterThan(v, zero);
127+
if (Sse2.MoveMask(v) != 0)
128+
return false;
129+
ptr += Vector128<sbyte>.Count;
130+
pixcnt -= Vector128<uint>.Count;
131+
}
132+
}
133+
134+
while (pixcnt > 0)
135+
{
136+
if (ptr[3] > 0)
137+
return false;
138+
ptr += sizeof(uint);
139+
pixcnt--;
140+
}
141+
45142
return true;
46143
}
47144

SvgFileType/Extensions/SvgDocumentExtensions.cs

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Drawing;
6+
using System.Threading;
67
using Svg;
78
using SvgFileTypePlugin.Import;
89

@@ -26,27 +27,57 @@ public static IDisposable UseSetRasterDimensions(this SvgDocument svg, SvgImport
2627
ArgumentNullException.ThrowIfNull(svg);
2728
ArgumentNullException.ThrowIfNull(config);
2829

29-
int width = config.RasterWidth;
30-
int height = config.RasterHeight;
31-
SvgAspectRatio originalAspectRatio = svg.AspectRatio;
32-
SizeF originalSize = svg.GetDimensions();
33-
SvgViewBox originalViewbox = svg.ViewBox;
34-
SizeF rasterSize = originalSize;
35-
svg.RasterizeDimensions(ref rasterSize, width, height);
36-
svg.Width = rasterSize.Width;
37-
svg.Height = rasterSize.Height;
38-
svg.ViewBox = new SvgViewBox(0, 0, originalSize.Width, originalSize.Height);
39-
SvgPreserveAspectRatio aspectRatio = config.PreserveAspectRatio
30+
SvgAspectRatio origAR = svg.AspectRatio;
31+
SizeF origSize = svg.GetDimensions();
32+
SizeF rasterSize = origSize;
33+
SvgViewBox origViewBox = svg.ViewBox;
34+
if (origViewBox.IsEmpty() && !origSize.IsEmpty)
35+
svg.ViewBox = new SvgViewBox(0, 0, origSize.Width, origSize.Height);
36+
svg.RasterizeDimensions(ref rasterSize, config.RasterWidth, config.RasterHeight);
37+
svg.Width = new SvgUnit(SvgUnitType.User, rasterSize.Width);
38+
svg.Height = new SvgUnit(SvgUnitType.User, rasterSize.Height);
39+
svg.AspectRatio = new SvgAspectRatio(config.PreserveAspectRatio
4040
? SvgPreserveAspectRatio.xMinYMin
41-
: SvgPreserveAspectRatio.none;
42-
svg.AspectRatio = new SvgAspectRatio(aspectRatio);
43-
return Utils.DisposableFromAction(() =>
41+
: SvgPreserveAspectRatio.none);
42+
return new DisposableAction(() =>
4443
{
4544
// Restore the original values back
46-
svg.AspectRatio = originalAspectRatio;
47-
svg.Width = originalSize.Width;
48-
svg.Height = originalSize.Height;
49-
svg.ViewBox = originalViewbox;
45+
svg.AspectRatio = origAR;
46+
svg.Width = origSize.Width;
47+
svg.Height = origSize.Height;
48+
svg.ViewBox = origViewBox;
5049
});
5150
}
51+
52+
private sealed class DisposableAction : IDisposable
53+
{
54+
private Action? _dispose;
55+
private readonly Lock @lock = new();
56+
57+
public DisposableAction(Action dispose)
58+
{
59+
ArgumentNullException.ThrowIfNull(dispose);
60+
61+
_dispose = dispose;
62+
}
63+
64+
public void Dispose()
65+
{
66+
if (_dispose is null)
67+
{
68+
return;
69+
}
70+
71+
@lock.Enter();
72+
try
73+
{
74+
_dispose.Invoke();
75+
}
76+
finally
77+
{
78+
_dispose = null;
79+
@lock.Exit();
80+
}
81+
}
82+
}
5283
}

SvgFileType/Extensions/SvgElementExtensions.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal static class SvgElementExtensions
1818

1919
private static readonly AttributesGetterDelegate GetElementAttributes = CreateGetterDelegate<AttributesGetterDelegate>("Attributes");
2020
private static readonly ElementNameGetterDelegate GetElementName = CreateGetterDelegate<ElementNameGetterDelegate>("ElementName");
21+
private static readonly XmlWriterSettings WriterSettings = new() { Encoding = Encoding.UTF8 };
2122

2223
public static string GetName(this SvgElement element)
2324
{
@@ -54,8 +55,7 @@ public static string GetXML_QuotedFuncIRIHack(this SvgElement svg)
5455
ArgumentNullException.ThrowIfNull(svg);
5556

5657
using InvariantUtf8StringWriter writer = new InvariantUtf8StringWriter();
57-
XmlWriterSettings xmlWriterSettings = new() { Encoding = Encoding.UTF8 };
58-
using (XmlWriter xmlWriter = new CustomXmlWriter(XmlWriter.Create(writer, xmlWriterSettings)))
58+
using (XmlWriter xmlWriter = new CustomXmlWriter(XmlWriter.Create(writer, WriterSettings)))
5959
{
6060
svg.Write(xmlWriter);
6161
xmlWriter.Flush();
@@ -69,8 +69,7 @@ public static void WriteXML_QuotedFuncIRIHack(this SvgElement svg, Stream output
6969
ArgumentNullException.ThrowIfNull(svg);
7070

7171
using InvariantUtf8StreamWriter writer = new InvariantUtf8StreamWriter(output);
72-
XmlWriterSettings xmlWriterSettings = new() { Encoding = Encoding.UTF8 };
73-
using (XmlWriter xmlWriter = new CustomXmlWriter(XmlWriter.Create(writer, xmlWriterSettings)))
72+
using (XmlWriter xmlWriter = new CustomXmlWriter(XmlWriter.Create(writer, WriterSettings)))
7473
{
7574
svg.Write(xmlWriter);
7675
xmlWriter.Flush();
@@ -87,6 +86,8 @@ private static T CreateGetterDelegate<T>(string propertyName) where T : Delegate
8786
return getter.CreateDelegate<T>();
8887
}
8988

89+
#region InvariantUtf8StreamWriter
90+
9091
private sealed class InvariantUtf8StreamWriter(Stream stream) : StreamWriter(stream, Encoding.UTF8, leaveOpen: true)
9192
{
9293
private readonly Stream stream = stream;
@@ -102,11 +103,21 @@ protected override void Dispose(bool disposing)
102103
}
103104
}
104105

106+
#endregion
107+
108+
#region InvariantUtf8StringWriter
109+
105110
private sealed class InvariantUtf8StringWriter() : StringWriter(CultureInfo.InvariantCulture)
106111
{
107112
public override Encoding Encoding => Encoding.UTF8;
108113
}
109114

115+
#endregion
116+
117+
#region CustomXmlWriter
118+
119+
// Workaround for removing quotes from quoted FuncIRI(s).
120+
110121
private sealed class CustomXmlWriter(XmlWriter writer) : XmlWriter
111122
{
112123
private readonly XmlWriter writer = writer;
@@ -138,4 +149,6 @@ public override void WriteString(string? text)
138149
public override void WriteSurrogateCharEntity(char lowChar, char highChar) => writer.WriteSurrogateCharEntity(lowChar, highChar);
139150
public override void WriteWhitespace(string? ws) => writer.WriteWhitespace(ws);
140151
}
152+
153+
#endregion
141154
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2025 Osman Tunçelli. All rights reserved.
2+
// Use of this source code is governed by GNU General Public License (GPL-2.0) that can be found in the COPYING file.
3+
4+
using System.Runtime.CompilerServices;
5+
using Svg;
6+
7+
namespace SvgFileTypePlugin.Extensions;
8+
9+
internal static class SvgViewBoxExtensions
10+
{
11+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
12+
public static bool IsEmpty(this SvgViewBox vb)
13+
{
14+
return vb.Width < float.Epsilon || vb.Height < float.Epsilon;
15+
}
16+
}

SvgFileType/FodyWeavers.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
3-
<ILMerge IncludeAssemblies="Svg|ExCss|resvg.net|BitmapVectorizer|SimplePaletteQuantizer|PaintDotNet.IndirectUI.Fluent" NamespacePrefix="" IncludeResources="\.(resources|dtd|zip)$" CompactMode="0" FullImport="1" />
3+
<ILMerge IncludeAssemblies="Svg|ExCss|resvg.net|BitmapVectorizer|SimplePaletteQuantizer|PaintDotNet.IndirectUI.Fluent" NamespacePrefix="" IncludeResources="\.(resources|dtd|zip)$" CompactMode="0" FullImport="0" />
44
</Weavers>

0 commit comments

Comments
 (0)