用于 ASP.NET 的 System.Drawing 的替代方案?

发布于 2024-08-06 08:57:32 字数 671 浏览 11 评论 0原文

经过几天的追踪奇怪的 GDI+ 错误后,我在

不支持在 Windows 或 ASP.NET 服务中使用 System.Drawing 命名空间中的类。尝试在这些应用程序类型之一中使用这些类可能会产生意外的问题,例如服务性能下降和运行时异常。

我不知道在这种情况下“ASP.NET 服务”是否意味着“Web 应用程序”,但“服务性能下降”似乎涵盖了“GDI+ 中发生的一般错误”和“内存不足”错误的随机分类我的应用程序抛出间歇性的、不可重现的错误,读取和写入 JPEG 图像(在许多情况下)实际上是由 System.Drawing.Imaging 首先创建的。

那么 - 如果 GDI+ 无法在 Web 应用程序中可靠地读取和写入 JPEG 文件,我应该使用什么来代替?

我希望用户能够上传图像(需要 JPEG,其他格式也不错) -have),可靠地重新采样,并在出现问题时显示有用的错误消息。有什么想法吗? WPF 中的 System.Media 命名空间值得考虑吗?

编辑:是的,我知道 GDI+“大多数时候”都有效。这还不够好,因为当它失败时,它会以一种无法优雅地隔离或恢复的方式进行。我对适合您的 GDI+ 代码示例不感兴趣:我正在寻找用于图像处理的替代库。

After several days of tracking down bizarre GDI+ errors, I've stumbled across this little gem on MSDN:

Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.

I don't know whether "ASP.NET service" means "web application" in this context, but "diminished service performance" certainly seems to cover the random assortment of "A generic error occurred in GDI+" and "Out of memory" errors that my app is throwing - intermittent, non-reproducible errors reading and writing JPEG images that - in many cases - were actually created by System.Drawing.Imaging in the first place.

So - if GDI+ can't read and write JPEG files reliably in a Web app, what should I be using instead?

I want users to be able to upload images (JPEG required, other formats nice-to-have), resample them reliably, and display useful error messages if anything goes wrong. Any ideas? Are the System.Media namespaces from WPF worth considering?

EDIT: Yeah, I know GDI+ works "most of the time". That's not good enough, because when it fails, it does so in a way that's impossible to isolate or recover from gracefully. I am not interested in examples of GDI+ code that works for you: I am looking for alternative libraries to use for image processing.

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

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

发布评论

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

评论(8

乱了心跳 2024-08-13 08:57:33

有一篇很棒的博客文章,其中包含有关通过 Interop 使用 ImageMagick 图形库 的 C# 代码,位于十大软件博客。这篇文章专门讨论在 Mono 下的 Linux 上运行 ASP.net;但是,C# 代码应该完全可以复制粘贴,如果您在引用窗口二进制文件 (DLL) 的 Windows 下运行,则唯一需要更改的是 Interop 属性。

ImageMagick® 是一款用于创建、编辑、撰写或转换的软件套件
位图图像。它可以读取和写入多种格式的图像
(超过 100 个)包括 DPX、EXR、GIF、JPEG、JPEG-2000、PDF、PhotoCD、
PNG、Postscript、SVG 和 TIFF。使用 ImageMagick 调整大小、翻转、
镜像、旋转、扭曲、剪切和变换图像,调整图像
颜色,应用各种特殊效果,或绘制文本,线条,多边形,
椭圆和贝塞尔曲线。

Codeplex 上还有一个 ImageMagick .Net 开发项目,它为您提供了一切。但自 2009 年以来,它并没有表现出积极的开发,因此它可能落后于当前的 ImageMagick 库版本。对于一个简单的调整大小例程,我可能会坚持使用互操作。您只需要仔细观察您的实现是否存在内存泄漏或未释放的资源(库本身经过了社区的充分测试和审查)。

该库是免费且开源的。 Apache 2 许可证似乎与个人和商业目的兼容。请参阅 ImageMagick 许可页面

该库是完全跨平台的,并实现了许多在 GDI+ 中未找到的强大图像处理和转换例程(或未在 Mono 下实现),并且作为 ASP.net 图像处理的替代方案享有盛誉。

更新:看起来这里有 .NET 包装器的更新版本:http://magick.codeplex.com/

There is an excellent blog post including C# code about using the ImageMagick graphics library through Interop over at TopTen Software Blog. This post deals specifically with running ASP.net on linux under mono; however, the C# code should be perfectly copy-paste-able, the only thing you'll need to change is the Interop attributes if you are running under windows referencing a window binary (DLL).

ImageMagick® is a software suite to create, edit, compose, or convert
bitmap images. It can read and write images in a variety of formats
(over 100) including DPX, EXR, GIF, JPEG, JPEG-2000, PDF, PhotoCD,
PNG, Postscript, SVG, and TIFF. Use ImageMagick to resize, flip,
mirror, rotate, distort, shear and transform images, adjust image
colors, apply various special effects, or draw text, lines, polygons,
ellipses and Bézier curves.

There is also an ImageMagick .Net development project on codeplex that wraps up everything for you. But it doesn't show active development since 2009, so it may be lagging behind the current ImageMagick library version. For a small trivial resizing routine, I'd probably stick with the interop. You just need to watch your implementation carefully for your own memory leak or unreleased resources (the library itself is well tested and vetted by the community).

The library is free and open source. The Apache 2 license appears to be compatible with both personal and commercial purposes. See ImageMagick License Page.

The library is totally cross platform and implements many powerful image handling and transformation routines that are not found in GDI+ (or not implemented under mono) and has a good reputation as an alternative for ASP.net image processing.

Update: Looks like there is an updated version of a .NET wrapper here: http://magick.codeplex.com/

风柔一江水 2024-08-13 08:57:33

是的,使用 WPF System.Windows.Media 类。由于得到全面管理,它们不会遇到与 GDI 相同的问题。

以下是我用来渲染渐变的一些 MVC 代码的摘录,让您了解如何从 WPF Visual 转换为 PNG:

using System;
using System.IO;
using System.Web.Mvc;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace MyMvcWebApp.Controllers
{
    public class ImageGenController : Controller
    {
        // GET: ~/ImageGen/Gradient?color1=red&color2=pink
        [OutputCache(CacheProfile = "Image")]
        public ActionResult Gradient(Color color1, Color color2, int width = 1, int height = 30, double angle = 90)
        {
            var visual = new DrawingVisual();
            using (DrawingContext dc = visual.RenderOpen())
            {
                Brush brush = new LinearGradientBrush(color1, color2, angle);
                dc.DrawRectangle(brush, null, new Rect(0, 0, width, height));
            }

            return new FileStreamResult(renderPng(visual, width, height), "image/png");
        }

        static Stream renderPng(Visual visual, int width, int height)
        {
            var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default);
            rtb.Render(visual);

            var frame = BitmapFrame.Create(rtb);
            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(frame);

            var stream = new MemoryStream();
            encoder.Save(stream);
            stream.Position = 0;

            return stream;
        }
    }
}

Yes, use the WPF System.Windows.Media classes. Being fully managed they don't suffer the same problems as the GDI stuff.

Here's an excerpt from some MVC code I use to render gradients, to give you an idea how to get from a WPF Visual to a PNG:

using System;
using System.IO;
using System.Web.Mvc;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace MyMvcWebApp.Controllers
{
    public class ImageGenController : Controller
    {
        // GET: ~/ImageGen/Gradient?color1=red&color2=pink
        [OutputCache(CacheProfile = "Image")]
        public ActionResult Gradient(Color color1, Color color2, int width = 1, int height = 30, double angle = 90)
        {
            var visual = new DrawingVisual();
            using (DrawingContext dc = visual.RenderOpen())
            {
                Brush brush = new LinearGradientBrush(color1, color2, angle);
                dc.DrawRectangle(brush, null, new Rect(0, 0, width, height));
            }

            return new FileStreamResult(renderPng(visual, width, height), "image/png");
        }

        static Stream renderPng(Visual visual, int width, int height)
        {
            var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default);
            rtb.Render(visual);

            var frame = BitmapFrame.Create(rtb);
            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(frame);

            var stream = new MemoryStream();
            encoder.Save(stream);
            stream.Position = 0;

            return stream;
        }
    }
}
尾戒 2024-08-13 08:57:33

您可以在这里找到 Microsoft 员工写的一篇非常好的文章: 使用 WPF/WIC 而不是 GDI+ 调整服务器中的图像大小 建议使用 WPF 而不是 GDI+。更多的是关于缩略图,但总体上是相同的问题。

无论如何,最后它指出了这一点:

我联系了 WPF 团队以最终确定这是否是
支持。不幸的是,事实并非如此,并且文档正在编写中
相应更新。对于这可能造成的任何混乱,我深表歉意
造成的。我们正在寻找让这个故事更容易被接受的方法
未来。

所以 WPF 在 Web 应用程序中也不支持,但我仍然相信 :-S

You can find a very good article from a Microsoft Employee here: Resizing images from the server using WPF/WIC instead of GDI+ that proposes to use WPF instead of GDI+. It's more about thumbnailing but it's overall the same issues.

Anyway, at the end it states this:

I contacted the WPF team to have the final word on whether this is
supported. Unfortunately, it's not, and the documentation is being
updated accordingly. I apologize about any confusion this may have
caused. We're looking at ways to make that story more acceptable in
the future.

So WPF is also unsupported in web apps and still is I believe :-S

因为看清所以看轻 2024-08-13 08:57:33

ImageSharp

ImageSharp 是一个开源跨平台 2D 图形库。它是在新的 .NET 标准之上用 C# 编写的,不依赖于任何特定于操作系统的 API。

它目前仍在 MyGet 上预发布(您必须在 VS 选项或 NuGet.config 文件中添加包源),但我们已经在使用它并取得了一些非常积极的结果。

ImageSharp

ImageSharp is an open-source cross-platform 2D graphics library. It's written in C# on top of the new .NET Standard, with no dependency on any OS-specific API.

It's currently still in pre-release on MyGet (you'll have to add the package source in the VS options or a NuGet.config file), but we are already using it with some very positive results.

蹲墙角沉默 2024-08-13 08:57:33

我读到的大多数问题都与资源处理不当有关。

我一次又一次地使用此代码的变体,网络应用程序没有出现任何问题:

public void GenerateThumbNail(HttpPostedFile fil, string sPhysicalPath, 
                              string sOrgFileName,string sThumbNailFileName,
                              System.Drawing.Imaging.ImageFormat oFormat, int rez)
{

    try
    {

        System.Drawing.Image oImg = System.Drawing.Image.FromStream(fil.InputStream);

        decimal pixtosubstract = 0;
        decimal percentage;

        //default
        Size ThumbNailSizeToUse = new Size();
        if (ThumbNailSize.Width < oImg.Size.Width || ThumbNailSize.Height < oImg.Size.Height)
        {
            if (oImg.Size.Width > oImg.Size.Height)
            {
                percentage = (((decimal)oImg.Size.Width - (decimal)ThumbNailSize.Width) / (decimal)oImg.Size.Width);
                pixtosubstract = percentage * oImg.Size.Height;
                ThumbNailSizeToUse.Width = ThumbNailSize.Width;
                ThumbNailSizeToUse.Height = oImg.Size.Height - (int)pixtosubstract;
            }
            else
            {
                percentage = (((decimal)oImg.Size.Height - (decimal)ThumbNailSize.Height) / (decimal)oImg.Size.Height);
                pixtosubstract = percentage * (decimal)oImg.Size.Width;
                ThumbNailSizeToUse.Height = ThumbNailSize.Height;
                ThumbNailSizeToUse.Width = oImg.Size.Width - (int)pixtosubstract;
            }

        }
        else
        {
            ThumbNailSizeToUse.Width = oImg.Size.Width;
            ThumbNailSizeToUse.Height = oImg.Size.Height;
        }

        Bitmap bmp = new Bitmap(ThumbNailSizeToUse.Width, ThumbNailSizeToUse.Height);
        bmp.SetResolution(rez, rez);
        System.Drawing.Image oThumbNail = bmp;

        bmp = null;

        Graphics oGraphic = Graphics.FromImage(oThumbNail);

        oGraphic.CompositingQuality = CompositingQuality.HighQuality;

        oGraphic.SmoothingMode = SmoothingMode.HighQuality;

        oGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;

        Rectangle oRectangle = new Rectangle(0, 0, ThumbNailSizeToUse.Width, ThumbNailSizeToUse.Height);

        oGraphic.DrawImage(oImg, oRectangle);

        oThumbNail.Save(sPhysicalPath  + sThumbNailFileName, oFormat);

        oImg.Dispose();

    }
    catch (Exception ex)
    {
        Response.Write(ex.Message);
    }

}

Most of the issues I have read about pertain to resources not being disposed properly.

I have used variants of this code time and time again with no issues from web applications:

public void GenerateThumbNail(HttpPostedFile fil, string sPhysicalPath, 
                              string sOrgFileName,string sThumbNailFileName,
                              System.Drawing.Imaging.ImageFormat oFormat, int rez)
{

    try
    {

        System.Drawing.Image oImg = System.Drawing.Image.FromStream(fil.InputStream);

        decimal pixtosubstract = 0;
        decimal percentage;

        //default
        Size ThumbNailSizeToUse = new Size();
        if (ThumbNailSize.Width < oImg.Size.Width || ThumbNailSize.Height < oImg.Size.Height)
        {
            if (oImg.Size.Width > oImg.Size.Height)
            {
                percentage = (((decimal)oImg.Size.Width - (decimal)ThumbNailSize.Width) / (decimal)oImg.Size.Width);
                pixtosubstract = percentage * oImg.Size.Height;
                ThumbNailSizeToUse.Width = ThumbNailSize.Width;
                ThumbNailSizeToUse.Height = oImg.Size.Height - (int)pixtosubstract;
            }
            else
            {
                percentage = (((decimal)oImg.Size.Height - (decimal)ThumbNailSize.Height) / (decimal)oImg.Size.Height);
                pixtosubstract = percentage * (decimal)oImg.Size.Width;
                ThumbNailSizeToUse.Height = ThumbNailSize.Height;
                ThumbNailSizeToUse.Width = oImg.Size.Width - (int)pixtosubstract;
            }

        }
        else
        {
            ThumbNailSizeToUse.Width = oImg.Size.Width;
            ThumbNailSizeToUse.Height = oImg.Size.Height;
        }

        Bitmap bmp = new Bitmap(ThumbNailSizeToUse.Width, ThumbNailSizeToUse.Height);
        bmp.SetResolution(rez, rez);
        System.Drawing.Image oThumbNail = bmp;

        bmp = null;

        Graphics oGraphic = Graphics.FromImage(oThumbNail);

        oGraphic.CompositingQuality = CompositingQuality.HighQuality;

        oGraphic.SmoothingMode = SmoothingMode.HighQuality;

        oGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;

        Rectangle oRectangle = new Rectangle(0, 0, ThumbNailSizeToUse.Width, ThumbNailSizeToUse.Height);

        oGraphic.DrawImage(oImg, oRectangle);

        oThumbNail.Save(sPhysicalPath  + sThumbNailFileName, oFormat);

        oImg.Dispose();

    }
    catch (Exception ex)
    {
        Response.Write(ex.Message);
    }

}
暮凉 2024-08-13 08:57:33

您可以查看 http://gd-sharp.sourceforge.net/ 这是GD 库的包装器。我还没有测试过,但看起来很有希望。

You may have a look at http://gd-sharp.sourceforge.net/ which is a wrapper for the GD library. I haven't tested it but it seems promising.

羅雙樹 2024-08-13 08:57:33

我在 ASP.Net Web 服务器环境中的 Cairo 库 (http://www.cairgraphics.org) 中表现良好。实际上,我从 WPF 搬到了开罗,因为 WPF 对于基于 Web 的内容的内存使用模型很差。

WPF 实际上往往会导致工作进程内存不足。没有一个 WPF 对象实现 IDisposable,其中许多对象引用仅通过终结器释放的非托管内存。大量使用 WPF(特别是如果您的服务器的 CPU 负担很重)最终会耗尽内存,因为您的终结器队列已饱和。例如,当我分析我的应用程序时,终结队列上有超过 50,000 个对象,其中许多对象保存对非托管内存的引用。 Cairo 对我来说表现得更好,而且它的内存使用模式比 WPF 的更可预测。

如果您对使用 cairo 感兴趣,请从 GTK+ 的网站获取库。他们有 x86 和 x64 二进制文件集。

唯一的缺点是 cairo 本身无法读取/写入 JPG;但是,您可以轻松地调整 WPF 的内容来读取/写入 JPG,并使用 Cairo 进行重新采样/缩放/绘图/其他操作。

I've had good behavior from the Cairo library (http://www.cairographics.org) in an ASP.Net webserver environment. I actually moved to cairo from WPF due to WPF's poor memory usage model for web-based stuff.

WPF actually tends to run your worker process out of memory. None of the WPF objects implement IDisposable, and many of them reference unmanaged memory that's only freed via a finalizer. Heavy use of WPF (especially if your server is significantly CPU-taxed) will eventually run you out of memory because your finalizer queue gets saturated. When I was profiling my app, for instance, the finalization queue had upwards of 50,000 objects on it, many of them holding references to unmanaged memory. Cairo has behaved much better for me, and its memory usage pattern has been much more predictable than WPF's.

If you're interested in using cairo, grab the libs from GTK+'s website. They have an x86 as well as an x64 set of binaries.

The only downside is that cairo can't read/write JPG natively; however, you could easily adapt WPF's stuff for reading/writing JPG and do the resampling/scaling/drawing/whatever else using Cairo.

看透却不说透 2024-08-13 08:57:33

Aspose.Drawing 是 System.Drawing 的直接替代品,它是完全托管的,可以在网络应用程序中安全使用。 (我是开发人员之一。)

Aspose.Drawing is a drop-in replacement for System.Drawing that is fully managed and can be safely used in web applications. (I'm one of the developers.)

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