Skip to content

Commit 9ed2b89

Browse files
committed
内置 wmf 转换支持
1 parent 46cfb5e commit 9ed2b89

6 files changed

Lines changed: 330 additions & 2 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Drawing;
4+
using System.Drawing.Imaging;
5+
using System.IO;
6+
using System.Runtime.Versioning;
7+
8+
namespace DotNetCampus.MediaConverters.Imaging.Optimizations;
9+
10+
/// <summary>
11+
/// 增强图元优化方法,用于优化 emf 和 wmf 图片
12+
/// </summary>
13+
public static class EnhancedGraphicsMetafileOptimization
14+
{
15+
public static ImageFileOptimizationResult ConvertWmfOrEmfToPngFile(FileInfo file, DirectoryInfo workingFolder)
16+
{
17+
// 在 Windows 上,直接使用 GDI+ 将 WMF 或 EMF 文件转换为 PNG 文件
18+
if (OperatingSystem.IsWindowsVersionAtLeast(6, 1))
19+
{
20+
return ConvertInWindows(file, workingFolder);
21+
}
22+
23+
if (OperatingSystem.IsLinux())
24+
{
25+
// 在 Linux 上,先尝试使用 Inkscape 进行转换,如失败,再使用 libwmf 进行转换
26+
return ConvertInLinux(file, workingFolder);
27+
}
28+
29+
return new ImageFileOptimizationResult()
30+
{
31+
OptimizedImageFile = null,
32+
FailureReason = ImageFileOptimizationFailureReason.NotSupported
33+
};
34+
}
35+
36+
[SupportedOSPlatform("linux")]
37+
private static ImageFileOptimizationResult ConvertInLinux(FileInfo file, DirectoryInfo workingFolder)
38+
{
39+
// 在 Linux 上,先尝试使用 Inkscape 进行转换,如失败,再使用 libwmf 进行转换
40+
// 调用 Inkscape 进行转换
41+
var svgFile = Path.Join(workingFolder.FullName, $"{Path.GetFileNameWithoutExtension(file.Name)}_{Path.GetRandomFileName()}.svg");
42+
43+
{
44+
var processStartInfo = new ProcessStartInfo("inkscape")
45+
{
46+
ArgumentList =
47+
{
48+
"--export-plain-svg",
49+
$"--export-filename={svgFile}",
50+
file.FullName,
51+
}
52+
};
53+
try
54+
{
55+
using var process = Process.Start(processStartInfo);
56+
process?.WaitForExit(TimeSpan.FromSeconds(5));
57+
if (process?.ExitCode == 0 && File.Exists(svgFile))
58+
{
59+
// 转换成功,再次执行 SVG 转 PNG 的转换
60+
var convertSvgToPngFile = ImageFileOptimization.ConvertSvgToPngFile(new FileInfo(svgFile), workingFolder);
61+
if (convertSvgToPngFile is not null)
62+
{
63+
return new ImageFileOptimizationResult()
64+
{
65+
OptimizedImageFile = convertSvgToPngFile
66+
};
67+
}
68+
}
69+
}
70+
catch (Exception e)
71+
{
72+
// 失败了,继续调用 libwmf 进行转换
73+
}
74+
}
75+
76+
// 继续执行 libwmf 的转换,此时不支持 emf 格式
77+
if (string.Equals(file.Extension, ".emf"))
78+
{
79+
return new ImageFileOptimizationResult()
80+
{
81+
OptimizedImageFile = null,
82+
FailureReason = ImageFileOptimizationFailureReason.NotSupported
83+
};
84+
}
85+
86+
// 使用 libwmf 进行转换
87+
88+
{
89+
// ./wmf2svg -o 1.svg image.wmf
90+
var processStartInfo = new ProcessStartInfo("wmf2svg")
91+
{
92+
ArgumentList =
93+
{
94+
"-o",
95+
svgFile,
96+
file.FullName,
97+
}
98+
};
99+
100+
var fontFolder = Path.Join(AppContext.BaseDirectory, "fonts");
101+
if (Directory.Exists(fontFolder))
102+
{
103+
processStartInfo.ArgumentList.Add($"--wmf-fontdir={fontFolder}");
104+
}
105+
106+
using var process = Process.Start(processStartInfo);
107+
process?.WaitForExit(TimeSpan.FromSeconds(5));
108+
if (process?.ExitCode == 0 && File.Exists(svgFile))
109+
{
110+
// 转换成功,再次执行 SVG 转 PNG 的转换
111+
var convertedFile = ImageFileOptimization.FixSvgInvalidCharacter(new FileInfo(svgFile),workingFolder);
112+
113+
var convertSvgToPngFile = ImageFileOptimization.ConvertSvgToPngFile(convertedFile, workingFolder);
114+
if (convertSvgToPngFile is not null)
115+
{
116+
return new ImageFileOptimizationResult()
117+
{
118+
OptimizedImageFile = convertSvgToPngFile
119+
};
120+
}
121+
}
122+
}
123+
124+
return new ImageFileOptimizationResult()
125+
{
126+
OptimizedImageFile = null,
127+
FailureReason = ImageFileOptimizationFailureReason.NotSupported
128+
};
129+
}
130+
131+
[SupportedOSPlatform("windows6.1")]
132+
private static ImageFileOptimizationResult ConvertInWindows(FileInfo file, DirectoryInfo workingFolder)
133+
{
134+
try
135+
{
136+
using var emf = new Bitmap(file.FullName);
137+
var convertedFile = Path.Join(workingFolder.FullName, $"GDI_{Path.GetRandomFileName()}.png");
138+
139+
// 将增强型图形元文件转成位图。
140+
emf.Save(convertedFile, ImageFormat.Png);
141+
142+
return new ImageFileOptimizationResult()
143+
{
144+
OptimizedImageFile = new FileInfo(convertedFile),
145+
};
146+
}
147+
catch (Exception e)
148+
{
149+
return new ImageFileOptimizationResult
150+
{
151+
OptimizedImageFile = null,
152+
Exception = e,
153+
FailureReason = ImageFileOptimizationFailureReason.GdiException
154+
};
155+
}
156+
}
157+
}

src/MediaConverters/MediaConverters.Lib/Imaging/Optimizations/ImageFileOptimization.cs

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55
using SixLabors.ImageSharp.PixelFormats;
66
using SixLabors.ImageSharp.Processing;
77

8+
using SkiaSharp;
9+
10+
using Svg.Skia;
11+
812
using System;
913
using System.IO;
1014
using System.Linq;
15+
using System.Threading;
1116
using System.Threading.Tasks;
17+
using System.Xml.Linq;
1218

1319
namespace DotNetCampus.MediaConverters.Imaging.Optimizations;
1420

@@ -44,11 +50,42 @@ public static async Task<ImageFileOptimizationResult> OptimizeImageFileAsync(Fil
4450
var file = imageFile;
4551
if (copyNewFile)
4652
{
47-
var newFilePath = Path.Join(workingFolder.FullName, $"{Path.GetRandomFileName()}_{imageFile.Name}");
53+
var newFilePath = Path.Join(workingFolder.FullName, $"Copy_{Path.GetRandomFileName()}_{imageFile.Name}");
4854
file.CopyTo(newFilePath);
4955
file = new FileInfo(newFilePath);
5056
}
5157

58+
if (IsExtension(".svg"))
59+
{
60+
// 如果是 svg 那就直接转换了,因为后续叠加特效等逻辑都不能支持 SVG 格式
61+
var outputFilePath = ConvertSvgToPngFile(file, workingFolder);
62+
if (outputFilePath is null)
63+
{
64+
return new ImageFileOptimizationResult()
65+
{
66+
OptimizedImageFile = null,
67+
FailureReason = ImageFileOptimizationFailureReason.NotSupported
68+
};
69+
}
70+
else
71+
{
72+
file = outputFilePath;
73+
}
74+
}
75+
else if (IsExtension(".wmf") ||
76+
IsExtension(".emf"))
77+
{
78+
var result = EnhancedGraphicsMetafileOptimization.ConvertWmfOrEmfToPngFile(file, workingFolder);
79+
if (result.OptimizedImageFile is not null)
80+
{
81+
file = result.OptimizedImageFile;
82+
}
83+
else
84+
{
85+
return result;
86+
}
87+
}
88+
5289
Image<Rgba32> image;
5390
try
5491
{
@@ -112,6 +149,11 @@ public static async Task<ImageFileOptimizationResult> OptimizeImageFileAsync(Fil
112149
image.Dispose();
113150
throw;
114151
}
152+
153+
bool IsExtension(string extension)
154+
{
155+
return string.Equals(file.Extension, extension, StringComparison.OrdinalIgnoreCase);
156+
}
115157
}
116158

117159
/// <summary>
@@ -221,4 +263,94 @@ public static void LimitImageSize(Image<Rgba32> image, int? maxImageWidth, int?
221263
/// 图片压缩的最大高度
222264
/// </summary>
223265
private const int MaxHeight = 2160;
266+
267+
/// <summary>
268+
/// 转换 svg 文件为 png 文件
269+
/// </summary>
270+
/// <param name="imageFile"></param>
271+
/// <param name="workingFolder"></param>
272+
/// <returns></returns>
273+
public static FileInfo? ConvertSvgToPngFile(FileInfo imageFile,
274+
DirectoryInfo workingFolder)
275+
{
276+
using var skSvg = new SKSvg();
277+
using var skPicture = skSvg.Load(imageFile.FullName);
278+
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(imageFile.Name);
279+
var outputFile = Path.Join(workingFolder.FullName, $"SVG_{Path.GetRandomFileName()}_{fileNameWithoutExtension}.png");
280+
var canSave = skSvg.Save(outputFile, SKColors.Transparent);
281+
if (canSave && File.Exists(outputFile))
282+
{
283+
return new FileInfo(outputFile);
284+
}
285+
286+
// 转换失败
287+
return null;
288+
}
289+
290+
public static async Task<FileInfo> FixSvgInvalidCharacterAsync(FileInfo svgFile,
291+
DirectoryInfo workingFolder)
292+
{
293+
using var fileStream = svgFile.OpenRead();
294+
using var streamReader = new StreamReader(fileStream);
295+
296+
var xDocument = await XDocument.LoadAsync(streamReader, LoadOptions.SetLineInfo, CancellationToken.None);
297+
bool anyUpdate = false;
298+
299+
foreach (var xElement in xDocument.Descendants("text"))
300+
{
301+
var value = xElement.Value;
302+
if (!string.IsNullOrEmpty(value) && value.Length > 0 && value[0] is var c && c == 0xFFFD)
303+
{
304+
// 0xFFFFD 是 utf8 特殊字符
305+
// 画出来就是�符号,不如删掉
306+
xElement.Value = string.Empty;
307+
308+
anyUpdate = true;
309+
}
310+
}
311+
312+
if (anyUpdate)
313+
{
314+
var convertedFile = Path.Join(workingFolder.FullName, $"FixSVG_{Path.GetRandomFileName()}.svg");
315+
using var stream = File.Create(convertedFile);
316+
await xDocument.SaveAsync(stream, SaveOptions.None, CancellationToken.None);
317+
return new FileInfo(convertedFile);
318+
}
319+
320+
// 啥都不用改,返回原图
321+
return svgFile;
322+
}
323+
324+
public static FileInfo FixSvgInvalidCharacter(FileInfo svgFile,
325+
DirectoryInfo workingFolder)
326+
{
327+
using var fileStream = svgFile.OpenRead();
328+
using var streamReader = new StreamReader(fileStream);
329+
330+
var xDocument = XDocument.Load(streamReader, LoadOptions.SetLineInfo);
331+
bool anyUpdate = false;
332+
333+
foreach (var xElement in xDocument.Descendants("text"))
334+
{
335+
var value = xElement.Value;
336+
if (!string.IsNullOrEmpty(value) && value.Length > 0 && value[0] is var c && c == 0xFFFD)
337+
{
338+
// 0xFFFFD 是 utf8 特殊字符
339+
// 画出来就是�符号,不如删掉
340+
xElement.Value = string.Empty;
341+
342+
anyUpdate = true;
343+
}
344+
}
345+
346+
if (anyUpdate)
347+
{
348+
var convertedFile = Path.Join(workingFolder.FullName, $"FixSVG_{Path.GetRandomFileName()}.svg");
349+
xDocument.Save(convertedFile);
350+
return new FileInfo(convertedFile);
351+
}
352+
353+
// 啥都不用改,返回原图
354+
return svgFile;
355+
}
224356
}

src/MediaConverters/MediaConverters.Lib/Imaging/Optimizations/ImageFileOptimizationFailureReason.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@ public enum ImageFileOptimizationFailureReason
2626
/// 不支持的图片格式
2727
/// </summary>
2828
NotSupported,
29+
30+
/// <summary>
31+
/// GDI 发生异常
32+
/// </summary>
33+
GdiException,
2934
}

src/MediaConverters/MediaConverters.Lib/MediaConverters.Lib.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
14+
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.116.1" />
15+
<PackageReference Include="Svg.Skia" Version="3.0.4" />
16+
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
1417
</ItemGroup>
1518

1619
<PropertyGroup>

src/MediaConverters/MediaConverters.Tool/Contexts/MediaConverterErrorCode.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public readonly record struct MediaConverterErrorCode
3838
/// </summary>
3939
public static readonly ErrorCode NotSupported = new(1004, "Not supported operation");
4040

41+
public static readonly ErrorCode GdiException = new(1005, "Gdi exception");
42+
4143
/// <summary>
4244
/// 错误代码
4345
/// </summary>
@@ -48,6 +50,7 @@ public readonly record struct MediaConverterErrorCode
4850
/// </summary>
4951
public string Message { get; init; }
5052

53+
5154
public MediaConverterErrorCode(int code, string message)
5255
{
5356
Code = code;

0 commit comments

Comments
 (0)