随着科技的进步,越来越多新的、更先进的文件格式应运而生,就比如谷歌的 WebP 与苹果的 AVIF、HEIC 图片格式。
这些图片格式具有更高的压缩比、更低的图像精度损失,并且大部分先进的浏览器都已经支援这些新的格式。(Edge目前还不支持AVIF,垃圾微软!)
既然如此,那么该如何在 .NET 中处理这些新兴的图片格式呢? Magick.NET 类库或许可以帮你解决这些烦恼!

ImageMagick 是一款免费、开源、跨平台的图片处理工具,可以解析、压缩、转换几乎所有类型的图形文件
官方提供了控制台程序与运行库,可以轻松在各个平台处理图片,方便集成到各类服务中去。

不过 ImageMagick 官方只提供了执行程序和库文件,一般都是用命令行来进行调用,或者是集成到C、C++等程序中,并未直接提供基于 .NET 程序的接口。
所以有大佬基于 ImageMagick 的源码,创建了 Magick.NET 项目:
https://github.com/dlemstra/Magick.NET
https://www.nuget.org/packages?q=magick.net

安装 Magick.NET 运行环境

首先,Magick.NET 是针对于 ImageMagick 的链接库文件的包装,核心的图片解析、转换、存储、压缩等都是基于本地代码,所以需要依据目标客户端来选择 x86 或者是 x64 版本的运行库。

Magick.NET 是一个非常庞大的项目,其单环境的运行库文件目前已经达到了20MB。
如果你需要同时兼容x86和x64,则程序可能要带40MB的运行库。

而 ImageMagick 的运行库除了CPU版本不同,还额外分为 Q8Q16Q16-HDRI 这三个版本。
这三个版本的主要区别是对于图片像素精度的处理,Q8为8bit,Q16为16bit,Q16-HDRI为精度更高的32bit浮点值。
一般用Q16即可,否则内存占用太大(Q16-HD为Q8的4倍、Q16的2倍)。

推荐使用 Nuget 进行安装:
https://www.nuget.org/packages?q=magick.net

在搜索结果中,先安装 Magick.NET.Core ,然后选择 Magick.NET-质量-平台 的运行库。
质量一般选 Q16,平台一般选 x64
如果你不确定目标CPU的位数,或者需要兼容x86的老旧设备,则可以选择 AnyCPU
安装完成后,Nuget 会自动帮你下载和引用运行库,以及所依赖的 ImageMagick 的本地动态链接库。

选择完运行库后,你还可以根据运行的环境,选择以下两个增强用的类库:
Magick.NET.SystemDrawing = 提供一个扩展类,用于将 Magick.NET 的 MagickImage 图像类型转化为 .NET 所支持的 Bitmap 类型。
Magick.NET.SystemWindowsMedia = 提供一个扩展类,用于将 MagickImage 类型转化为 WPF 所支持的 BitmapSource 类型。

如果安装顺利完成,我们就可以测试能否解析 .NET 不支持的图片格式了:
Imports ImageMagick

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim fp = "x:\path\xxx.avif"
    Dim img As New MagickImage(fp)
    Dim bmp As Bitmap = img.ToBitmap()
    Me.BackgroundImage = bmp
End Sub
这里我们直接用 MagickImage 装载一个AVIF图片,然后再利用 Magick.NET.SystemDrawing 所提供的扩展方法 ToBitmapMagickImage 转化为 Bitmap,最后显示在窗体的背景图中。

代码示例

原谅我是个VB用户,不过VB语法与C#相差不大,各位应该能看懂吧。😏

设置缓存目录

在某些比较老旧的 Magick.NET 版本中,Magick.NET-Q16-AnyCPU.dll 是将链接库集成到程序集内的,只有在程序第一次解析时再将DLL文件释放到临时目录内进行加载的。
一般情况下,它都会将DLL放到系统的TEMP文件夹下,但你也可以手动设置这个缓存目录的位置,方便后续的其他操作(比如清理、更新内容什么的)。
Imports ImageMagick
Imports System.IO

' 将缓存目录设置为程序目录下的DLL文件夹
Dim cachePath = Path.Combine(Application.StartupPath, "dll")
If Not Directory.Exists(cachePath) Then Directory.CreateDirectory(cachePath)
MagickAnyCPU.CacheDirectory = cachePath
新版本的 Magick.NET 都是将 .NET 程序集与 DLL 运行库分开的,如果是 AnyCPU 的话,会同时包含x86和x64的DLL文件,但是占用空间也会是两倍了。

读取图片的几种方式

' 通过文件路径读取
Dim fp = "x:\path\xxx.avif"
Dim img As New MagickImage(fp) '初始化时直接读取图片数据
img.Read(fp) '等效方法,可以在初始化之后再读取图片数据

' 从Bytes中读取
Dim bs = IO.File.ReadAllBytes("x:\path\xxx.avif")
Dim img As New MagickImage(bs)

' 从文件流、内存流中读取
Dim ms As New MemoryStream(bs)
Dim img As New MagickImage(ms)

调整图片大小

Dim fp = "x:\path\xxx.avif"
Dim img As New MagickImage(fp)

' 设置参数为0时,会按另一个参数进行等比例缩放
img.Resize(1920, 0)
img.Resize(0, 1080)

' 保存编辑过的图片,可以是路径、文件流
' Magick.NET 会按文件扩展名来自动转换图片格式
img.Write("x:\path\new xxx.avif")
' 你也可以指定文件格式
img.Write("x:\path\new xxx.png", MagickFormat.Png)
' 还可以直接获取 Bytes
Dim bs1 As Byte() = img.ToByteArray()
Dim bs2 As Byte() = img.ToByteArray(MagickFormat.Bmp)
使用 Resize 可以快速调整图片大小,如果参数是0的话,就会根据另一个参数进行等比例调整,非常方便。

裁剪图片

Dim fp = "x:\path\xxx.avif"
Dim img As New MagickImage(fp)
' 从中间裁剪
img.Crop(500, 500, Gravity.Center)
Crop 可以裁剪图片到指定大小,Gravity.Center 指示图片以中间为基点开始裁剪,放弃周边的图像内容。

只加载图片信息

Dim fp = "x:\path\xxx.avif"
Dim img As New MagickImageInfo(fp)

MsgBox(img.Format.ToString) '图片格式
MsgBox(img.Width) '宽度
MsgBox(img.Height) '高度
MsgBox(img.Quality) '图片质量,只有部分格式有效
MagickImageInfo 类只加载图片头部数据,而不加载图片数据,可以用于预先判断图片类型、获取宽高等数据。

检测图片是否为动画

Dim fp = "x:\path\xxx.gif"

' .NET 中只能判断 GIF 图片是否为动画
Dim img = Image.FromFile(fp)
Dim isAnimate = ImageAnimator.CanAnimate(img)

' MagickImage 可以通过头部信息来确定图片内的帧数量
Dim minfos = MagickImageInfo.ReadCollection(fp)
MsgBox(minfos.Count) '获取帧数

获取图片所有帧与帧间隔

Dim fp = "x:\path\xxx.gif"
Dim mimgs = New MagickImageCollection(fp)

' 报告下一帧的间隔
For Each ming In mimgs
    MsgBox(ming.AnimationTicksPerSecond)
Next
MagickImageCollection 将解析一个图片所包含的所有帧图片,如果不是动画,则只返回一个主帧图(原图)。
可以通过循环迭代每一张图片,还可以通过图片的 AnimationTicksPerSecond 属性获取下一帧的间隔时间。
如果想要显示动画效果,则需要自己写绘图过程,暂时还没有比较方便的自动动画方式。
目前 MagickImage 可以正确解析 GIF、WEBP 的动画,但是无法解析 APNG 动画,因为 APNG 并没有被官方所承认,只是一个非行业标准动画格式。

读写 PDF 文件

ImageMagick 使用 Ghostscript 库来读取PDF文件,不过因为授权协议不同,所以 Ghostscript 的库文件需要你自行下载。
https://ghostscript.com/releases/gsdnld.html

根据自己的目标平台,选择下载 x86 或 x64 的 Ghostscript,将其中的 gsdll32.dll/gsdl64.dll/gswin32c.exe/gswin64c.exe 释放到主程序目录下(路径可以自定义),然后在程序初始化时设定 Ghostscript 运行库的路径。
' 在程序初始化时执行,请尽早执行
MagickNET.SetGhostscriptDirectory("x:\xxx\Ghostscript\")

' 直接通过 MagickImageCollection 加载PDF文件
Dim Pdf As New MagickImageCollection("x:\path\xxx.pdf", New MagickReadSettings() With {.Density = New Density(300)})

' 获取PDF文件内每一页的图像实例
For Each page In Pdf
    ' do someting
    page.ToBitmapWithDensity()
Next

' 你还可以在PDF文件中添加、插入新的图片
Pdf.Add(MagickImage) '在末尾添加图片
Pdf.Insert(0, MagickImage) '在指定位置添加图片
Pdf.RemoveAt(0) '删除指定图片

Pdf.Write("x:\path\new xxx.pdf") '保存修改

暂时先写这么多吧,我仅列出了 Magick.NET 最基础的用法,ImageMagick 的神奇功能还有许多,比如压缩、水印、虚化、添加各种特效等等,还请自行检阅 MagickImage 的内部方法。