.NET Core/.NET 6 號稱跨平台,但實際推進到 Linux,有些眉角遇上才會知道。

繼續嘗試在 CentOS 整合事務機掃描功能,掃描結果的圖檔格式是 TIFF,想寫段 C# 程式將它轉成 JPG,這用 Bitmap 來做是小事一椿。要在 .NET 6 使用 Bitmap,可安裝 System.Drawing.Common,寫幾行程式搞定:

TIFF 成功轉成 JPG,但出現一些編譯警示,提醒我們 Image.Save()、Bitmap、ImageFormat.Jpeg 這些 API 只能在 Windows 平台使用:

warning CA1416: This call site is reachable on all platforms. 'Image.Save(Stream, ImageFormat)' is only supported on: 'windows'.
warning CA1416: This call site is reachable on all platforms. 'Bitmap' is only supported on: 'windows'.
warning CA1416: This call site is reachable on all platforms. 'ImageFormat.Jpeg' is only supported on: 'windows'.

依據文件 System.Drawing.Common only supported on Windows,System.Drawing.Common 跨平台依賴 libgdiplus,它移植自 Windows 移植,是個三萬行 C 程式碼的程式庫,但功能不完整、測試不足,還依賴 cairo、pango 等原生程式庫,對部署環境要求較多,品質上無法跟其他 .NET 程式庫相比,故 System.Drawing.Common 只被定位成方便 .NET Framework 程式碼升級 .NET Core/.NET 5+ 繼續在 Windows 平台執行,不保證跨平台。從 .NET 6 起,若程式用到 System.Drawing.Common 不支援跨平台功能,將會出現編譯警告,在非 Windows 平台執行則會抛出 TypeInitializationException (InnerException 為 PlatformNotSupportedException):(下圖為文章開頭程式在 Linux 執行的結果)

幸好在 Coding4Fun 時發現,若在正式專案,天真地想把 .NET Framework 既有圖檔產生函式直接搬進 ASP.NET Core Docker, 遇到肯定手忙腳亂。

解決這個問題,治標做法是在 Linux 環境安裝 libdgiplus (參考:DotNetCore跨平台~System.DrawingCore部署Linux需要注意的 by 张占岭),並在 appName.runtimeconfig.json 加入:參考

{
   "runtimeOptions": {
      "configProperties": {
         "System.Drawing.EnableUnixSupport": true
      }
   }
}

不過,非 Windows 平台的 libgdiplus 已被放生,有 Bug 也不再修正,而 EnableUnixSupport 在未來版本可能被取消,非長久之計。因此,官方建議的治本之道是改用其他較可靠的跨平台程式庫,例如:

我這次的處理目標是 TIFF 轉 JPG,研究了一下,ImageSharp 最大優點是純 .NET 打造,不依賴其他原生程式庫,但目前不支援 TIFF,需自行整合額外程式庫(例如:LibTiff.Net),有個加入 TIFF 支援的 PR 已併入 master,但還沒正式發佈;SkiaSharp 基於 Google 的 Skia 2D 繪圖程式庫, 也不支援 TIFF,需自行整合;Magick.NET 基於跨平台的 ImageMagick 程式庫,支援檔案格式高達一百多種,但要在 Linux 平台使用需要額外步驟建立程式庫連結;Microsoft.Maui.Graphics 支援 Xamarin iOS/Android/Mac、WPF、UWP、WinForms、Linux,仍在實驗階段暫無技術支援,不過它重構自使用超過十年的程式碼,有一定可靠度,但它還太新,參考範例跟 API 文件稀少較難上手。

經過評估,我決定用 ImageSharp 的 2.0 Alpha 版,這個版本內建 TIFF 格式支援,用起來最省事。安裝 Alpha 版需指定指號及來源,指令為dotnet add package SixLabors.ImageSharp --version 2.0.0-alpha.0.124 --source https://www.myget.org/F/sixlabors/api/v3/index.json,安裝好 ImageSharp,簡單修改程式:

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;

using (var src = Image.Load("./sample.tiff"))
{
    using (var ms = new MemoryStream())
    {
        src.Save(ms, new JpegEncoder { Quality = 85 });
        File.WriteAllBytes("sample.jpg", ms.ToArray());
    }
}

在 Windows 測試成功後,將程式移到 CentOS 執行,因 Alpha 版來源不同,dotnet 無法自動還原 ImageSharp,需手動跑指定 --source 的 dotnet add package 指令,再遇到 error: NU1101: Unable to find package System.Runtime.CompilerServices.Unsafe. No packages exist with this id in source(s): https://www.myget.org/F/sixlabors/api/v3/index.json 錯誤,試著手動執行 dotnet add package System.Runtime.CompilerServices.Unsafe 從 NuGet 安裝 CompilerServices.Unsafe 後再試一次,錯誤消失,也順利完成轉檔!

一個簡單的用 C# 將 TIFF 轉 JPG 動作,我花了超過六個小時才成功在 Linux 上成功執行,這就是所謂「魔鬼都在細節裡」吧!

【延伸閱讀】

System.Drawing.Common is not supported on Linux, this article give some tips with a TIFF to JPG example.


Comments

# by Tim

請問一下,小弟有一個net core專案需要在上傳的底圖上加註一些符號,不知道暗黑大大有沒有什麼套件可以推荐的?

# by Jeffrey

to Tim, 如果只會在 Windows 跑,我建議用 System.Drawing 最省事,若要在 Linux 跑,我偏好 ImageSharp (純 .NET 不依賴原生程式庫,部署問題較少)

# by 過路人

ImageSharp 授權 Works in Source or Object form are split licensed and may be licensed under the Apache License, Version 2.0 or a Six Labors Commercial Use License. https://github.com/SixLabors/ImageSharp/blob/main/LICENSE

# by 6

有沒有大神能幫我,我在windows裡面居然還跑出System.PlatformNotSupportedException: System.Drawing.Common is not supported on this platform 快瘋了

# by Jeffrey

to 6, 能包個重現問題的小專案丟上 Github?

# by felix

to 6, check this https://devblogs.microsoft.com/dotnet/migrating-delegate-begininvoke-calls-for-net-core/

# by 路人甲

因 SkiaSharp 是 mit 授權,決定使用 但 在 linux 上 使用 SkiaSharp,仍有 native 元件的依存問題 得再額外安裝 nuget 套件 Package SkiaSharp.NativeAssets.Linux or SkiaSharp.NativeAssets.Linux.NoDependencies. https://github.com/mono/SkiaSharp/issues/1999

# by 路人甲

補上於 linux 上的錯誤 DllNotFoundException: Unable to load shared library 'libSkiaSharp' or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: liblibSkiaSharp: cannot open shared object file: No such file or directory

Post a comment