WPF 基礎繪圖 創建和加工圖片

本文將從控制檯開始,告訴大家一個非常簡單的 WPF 基礎繪圖方法,通過本文的方法可以調用 WPF 上層人類友好的方法,充分利用 GPU 資源,創建或加工圖片,最終結果可以輸出到本地文件,可支持編碼出多種不同的圖片格式

本文僅用到 WPF 的多媒體渲染層,在 WPF 的這一層上的 API 是人類友好的,直接咱使用的是就是熟悉的 DrawingContext 類型。通過 DrawingContext 進行畫線、畫矩形、畫幾何、畫圖片、畫文字,進行裁剪、變換、加特效等等,即可繪製出絢酷的界面效果。經由 RenderTargetBitmap 和 BitmapEncoder 即可將界面編碼爲圖片輸出到文件裏面

從本質上講,本文的方法裏面只是將 WPF 當成一個對 WIC (Windows Imaging Component) 層以及 DirectX 層的高級封裝庫,沒有用到 WPF 更多好用的功能

回到主題,先創建一個控制檯項目,在控制檯項目裏面打上 WPF 的負載,加上之後的 csproj 項目文件的代碼如下

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UseWpf>true</UseWpf>
  </PropertyGroup>

</Project>

在控制檯裏面要麼在 Main 函數標記 STAThread 特性,要麼自己新建一個 STA 線程,由於我之前的博客大量介紹的都是在 Main 函數上標記 STAThread 特性,於是我準備在這篇博客換一個玩法,就是自己新建一個 STA 線程。相對來說新建一個線程的代碼會稍微多一點點

var thread = new Thread(() =>
{
    ... // 等待添加更多代碼
});

thread.SetApartmentState(ApartmentState.STA);
thread.Start();

完成線程創建之後,即可在以上的 等待添加更多代碼 裏面加上 WPF 應用的初始化,在 WPF 應用的初始化裏面自動將包含大量的初始化工作,簡單的代碼如下

var thread = new Thread(() =>
{
    var application = new Application();
    application.Startup += Application_Startup;
    application.Run();
});

在 Startup 事件裏面執行真正的處理繪圖相關邏輯,以上代碼的 Application_Startup 的基礎實現邏輯如下

先創建 DrawingVisual 對象,通過此對象即可獲取到 DrawingContext 以執行繪圖邏輯

void Application_Startup(object sender, StartupEventArgs e)
{
    var drawingVisual = new DrawingVisual();
    ... // 等待添加更多代碼
}

從 DrawingVisual 裏面獲取 DrawingContext 的代碼如下

    var drawingVisual = new DrawingVisual();
    using (DrawingContext drawingContext = drawingVisual.RenderOpen())
    {
        ... // 等待添加更多代碼
    }

通過 DrawingContext 的 DrawXxx 系列方法即可繪製出有趣的界面效果,比如本文例子中繪製簡單的矩形的代碼

    using (var drawingContext = drawingVisual.RenderOpen())
    {
        drawingContext.DrawRectangle(Brushes.Black, pen: null, new Rect(0, 0, 1024, 768));
    }

實際使用中,可以在此替換爲你所需的各種代碼,包括先繪製圖片,再繪製文本,從而製作文本水印等等。也可以加上更多複雜的編程控制邏輯,繪製更復雜的圖片。如繪製從本地文件讀取出來的圖片,請參閱 WPF 通過 DrawingContext DrawImage 繪製圖片 以及更復雜的繪製邏輯請看 博客導航

完成繪製之後,可使用 RenderTargetBitmap 與 BitmapEncoder 配合,將繪製的內容寫入到本地圖片文件裏面

創建 RenderTargetBitmap 對象,給定尺寸,代碼如下

    // 畫布大小
    var drawingBounds = drawingVisual.Drawing.Bounds;
    // 修改爲固定的尺寸
    drawingBounds = new Rect(0, 0, 1024, 768);
    var renderTargetBitmap = new RenderTargetBitmap((int) drawingBounds.Width, (int) drawingBounds.Height, 96, 96, PixelFormats.Pbgra32);

以上代碼裏面的畫布大小有多個選項,一個就是使用繪製的範圍,繪製的範圍裏面可能控制性比較差,因爲會受到本身繪製的影響,可能存在繪製範圍超過了預期畫布尺寸,導致了輸出的圖片偏差。設置固定的尺寸大小,可以固定畫布的尺寸,相對來說比較常用

以上代碼裏面的 RenderTargetBitmap 的 96 指的是圖片的 DPI 值,默認情況下給 96 是比較符合視覺預期的。這裏的 DPI 不是屏幕的 DPI 值,一般不應該根據當前的顯示設備的 DPI 值賦值給到圖片上,應該一直保持是 96 的 DPI 值。這部分有一些前置的知識,更多還請自行查閱 DPI 相關知識

將剛纔繪製完成的 DrawingVisual 在 RenderTargetBitmap 渲染出來,代碼如下

    renderTargetBitmap.Render(drawingVisual);

現在已經拿到了 RenderTargetBitmap 對象,由於 RenderTargetBitmap 類型繼承自 BitmapSource 類型,因此這裏可以認爲已經拿到了 BitmapSource 對象

現在的 BitmapSource 對象還在內存裏面,想要將 BitmapSource 存放爲圖片文件需要經過圖片編碼步驟,將編碼之後的數據寫入到文件才能完成保存圖片文件的保存

圖片編碼可使用 BitmapEncoder 類型的對象進行編碼,常用的繼承自 BitmapEncoder 的 PngBitmapEncoder 或 JpegBitmapEncoder 等等類型。本文這裏通過 PngBitmapEncoder 編碼出 png 文件,代碼如下

    var pngBitmapEncoder = new PngBitmapEncoder();
    pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

    var file = "1.png";

    using (var fileStream = File.Create(file))
    {
        pngBitmapEncoder.Save(fileStream);
    }

通過以上方法即可將圖片編碼保存到 1.png 文件

使用本文以上的方法即可簡單從控制檯開始,使用 WPF 輔助繪製內容,將繪製的內容編碼爲圖片文件保存到本地文件

本文以上代碼都寫在一個 Program.cs 文件裏,代碼非常簡單

using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
var thread = new Thread(() =>
{
    var application = new Application();
    application.Startup += Application_Startup;
    application.Run();
});

void Application_Startup(object sender, StartupEventArgs e)
{
    var drawingVisual = new DrawingVisual();
    using (var drawingContext = drawingVisual.RenderOpen())
    {
        drawingContext.DrawRectangle(Brushes.Black, pen: null, new Rect(0, 0, 1024, 768));
    }

    // 畫布大小
    var drawingBounds = drawingVisual.Drawing.Bounds;
    // 修改爲固定的尺寸
    drawingBounds = new Rect(0, 0, 1024, 768);
    var renderTargetBitmap = new RenderTargetBitmap((int) drawingBounds.Width, (int) drawingBounds.Height, 96, 96, PixelFormats.Pbgra32);
    renderTargetBitmap.Render(drawingVisual);

    var pngBitmapEncoder = new PngBitmapEncoder();
    pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

    var file = "1.png";

    using (var fileStream = File.Create(file))
    {
        pngBitmapEncoder.Save(fileStream);
    }
}

thread.SetApartmentState(ApartmentState.STA);
thread.Start();

本文代碼放在 githubgitee 上,可以使用如下命令行拉取代碼

先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 1b8661bdbea21fbef432856ddc26999505144ba2

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換爲 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 1b8661bdbea21fbef432856ddc26999505144ba2

獲取代碼之後,進入 BayheelearchachearJobarhearne 文件夾,即可獲取到源代碼

更多 WPF 基礎繪圖請參閱 博客導航

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章