在 DLL 上使用 WPF 动态创建图像(而不是 GDI)

发布于 2024-07-18 23:29:23 字数 305 浏览 2 评论 0原文

我需要动态生成图像,在阅读此处 我意识到我可以使用 WPF 中的所有控件和布局来生成渲染,然后将其另存为 JPG。 我们的想法是使用它来代替 GDI+,这是相当原始的。

问题是,如何创建一个常规 dll 文件,该文件将以编程方式生成 WPF 画布,以便我可以向其中添加控件,然后将其输出到图像文件。 请记住,它将由 ASP.NET 应用程序使用。

有人有什么想法吗?

I need to generate an image dynamically, and after I read the tutorial here I realize i can use all the controls and layouts from WPF to generate my rendering, and then save it as a JPG.
The idea is to use this instead of GDI+, which is quite primitive.

The question is, how to I create a regular dll file that would generate programatically a WPF canvas so then i can add controls to it and then ouput that to an image file. Keep in mind that it would be consumed by an ASP.NET application.

Any ideas somebody?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

心安伴我暖 2024-07-25 23:29:24

这个例子有一个好的开始,但我发现它有很多不需要的垃圾。 最主要的是您不需要有一个单独的 WPF 项目。

要做的事情如下:

  • 在您的 Web 项目中引用PresentationCore、PresentationFramework 和WindowsBase。
  • 在 STA 线程中以编程方式创建 Canvas 和其他 WPF 对象。
  • 对它们调用一些特殊方法,以确保它们在 WPF 应用程序的上下文之外进行更新。
  • 使用 RenderTargetBitmap 将它们渲染为图像。
  • 关闭线程的调度程序。
  • 设置mime类型并使用ASP.NET输出图像。

为了提高效率,您可以重复使用同一线程,而不是为每个图像创建一个新线程。 在这种情况下,您只需要在关闭线程时清理调度程序。

这是我的完整工作代码:

using System;
using System.Web;
using System.Threading;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Documents;

public partial class _Default : System.Web.UI.Page
{
    private byte[] imageBuffer;

    public void Page_Load(object sender, EventArgs e)
    {
        this.RenderImage();

        Response.Clear();
        Response.ContentType = @"image/png";
        Response.BufferOutput = true;
        Response.BinaryWrite(this.imageBuffer);
        Response.Flush();
    }

    public void RenderImage()
    {
        Thread worker = new Thread(new ThreadStart(this.RenderImageWorker));
        worker.SetApartmentState(ApartmentState.STA);
        worker.Name = "RenderImageWorker";
        worker.Start();
        worker.Join();
    }

    public void RenderImageWorker()
    {
        Canvas imageCanvas = new Canvas { Width = 600, Height = 200, Background = Brushes.Azure };

        TextBlock tb = new TextBlock();
        tb.Width = (double)400;
        //tb.Height = (double)200;
        tb.TextAlignment = TextAlignment.Center;
        tb.Inlines.Add(new Run("This is "));
        tb.Inlines.Add(new Bold(new Run("bold")));
        tb.Inlines.Add(new Run(" text."));
        tb.FontSize = 30;
        tb.Foreground = Brushes.Blue;

        imageCanvas.Children.Add(tb);

        // Update layout
        imageCanvas.Measure(new Size(imageCanvas.Width, imageCanvas.Height));
        imageCanvas.Arrange(new Rect(new Size(imageCanvas.Width, imageCanvas.Height)));

        RenderTargetBitmap bitmapRenderer = new RenderTargetBitmap((int)imageCanvas.ActualWidth, (int)imageCanvas.ActualHeight, 96, 96, PixelFormats.Pbgra32);
        bitmapRenderer.Render(imageCanvas);

        PngBitmapEncoder png = new PngBitmapEncoder();
        png.Frames.Add(BitmapFrame.Create(bitmapRenderer));

        using (MemoryStream memoryStream = new MemoryStream())
        {
            png.Save(memoryStream);
            this.imageBuffer = memoryStream.ToArray();
        }

        if (bitmapRenderer.Dispatcher.Thread.IsAlive)
        {
            bitmapRenderer.Dispatcher.InvokeShutdown();
        }
    }
}

That example has a good start but I've found it's got a lot of unneeded junk along with it. The main thing is that you don't need to have a separate WPF project.

Here's what to do:

  • Reference PresentationCore, PresentationFramework and WindowsBase in your web project.
  • Create a Canvas and other WPF objects programmatically in an STA thread.
  • Call a few special methods on them to make sure they update outside of the context of a WPF app.
  • Render them to an image with RenderTargetBitmap.
  • Shut down the thread's dispatcher.
  • Set the mime type and output the image with ASP.NET.

To make this more efficient you could re-use the same thread rather than creating a new one for each image. In that case you would only need to clean up the dispatcher when you shut down the thread.

Here's the full working code I have:

using System;
using System.Web;
using System.Threading;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Documents;

public partial class _Default : System.Web.UI.Page
{
    private byte[] imageBuffer;

    public void Page_Load(object sender, EventArgs e)
    {
        this.RenderImage();

        Response.Clear();
        Response.ContentType = @"image/png";
        Response.BufferOutput = true;
        Response.BinaryWrite(this.imageBuffer);
        Response.Flush();
    }

    public void RenderImage()
    {
        Thread worker = new Thread(new ThreadStart(this.RenderImageWorker));
        worker.SetApartmentState(ApartmentState.STA);
        worker.Name = "RenderImageWorker";
        worker.Start();
        worker.Join();
    }

    public void RenderImageWorker()
    {
        Canvas imageCanvas = new Canvas { Width = 600, Height = 200, Background = Brushes.Azure };

        TextBlock tb = new TextBlock();
        tb.Width = (double)400;
        //tb.Height = (double)200;
        tb.TextAlignment = TextAlignment.Center;
        tb.Inlines.Add(new Run("This is "));
        tb.Inlines.Add(new Bold(new Run("bold")));
        tb.Inlines.Add(new Run(" text."));
        tb.FontSize = 30;
        tb.Foreground = Brushes.Blue;

        imageCanvas.Children.Add(tb);

        // Update layout
        imageCanvas.Measure(new Size(imageCanvas.Width, imageCanvas.Height));
        imageCanvas.Arrange(new Rect(new Size(imageCanvas.Width, imageCanvas.Height)));

        RenderTargetBitmap bitmapRenderer = new RenderTargetBitmap((int)imageCanvas.ActualWidth, (int)imageCanvas.ActualHeight, 96, 96, PixelFormats.Pbgra32);
        bitmapRenderer.Render(imageCanvas);

        PngBitmapEncoder png = new PngBitmapEncoder();
        png.Frames.Add(BitmapFrame.Create(bitmapRenderer));

        using (MemoryStream memoryStream = new MemoryStream())
        {
            png.Save(memoryStream);
            this.imageBuffer = memoryStream.ToArray();
        }

        if (bitmapRenderer.Dispatcher.Thread.IsAlive)
        {
            bitmapRenderer.Dispatcher.InvokeShutdown();
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文