【问题标题】:How to create a jpg image dynamically in memory with .NET?如何使用 .NET 在内存中动态创建 jpg 图像?
【发布时间】:2011-07-24 14:20:32
【问题描述】:

我有一个用 C# 编写的 .NET (3.5 SP1) 库 (DLL)。我必须通过一个具有以下签名的类方法来扩展这个库:

public byte[] CreateGridImage(int maxXCells, int maxYCells,
    int cellXPosition, int cellYPosition)
{
    ...
}

此方法应该执行以下操作:

  • 输入参数maxXCellsmaxYCells 定义单元格在X 和Y 方向上的大小。 maxXCellsmaxYCells 是每个方向的单元格数。单个细胞是方形的。 (所以这是一种不对称的棋盘。)
  • 输入参数cellXPositioncellYPosition 标识此网格中的一个特殊单元格,该单元格必须用叉号填充。
  • 不需要花哨的图形,实际上只需要白色背景上的黑色网格线和其中一个单元格中的 X。
  • 生成的图形必须为 jpg 格式。
  • 必须在内存中创建此图形,不得将任何内容存储在磁盘上的文件中,也不得在屏幕上绘制。
  • 该方法将生成的图像作为byte[]返回

我对 .NET 中的图形函数非常陌生,所以我的问题是:

  • 在没有额外第三方库的情况下,.NET 3.5 SP1 是否可以做到这一点(我想避免)?
  • 我必须遵循哪些基本步骤,以及我需要了解哪些重要的 .NET 命名空间、类和方法才能实现此目标(尤其是在“内存中”绘制线条和其他简单的图形元素并转换结果转换成 jpg 格式的字节数组)?

提前感谢您的建议!

【问题讨论】:

  • 各个单元格的宽度和高度应该是多少?我们知道它们应该是方形的,但我们仍然需要知道尺寸。
  • @Alison:单元格的宽度和高度(以像素为单位)将作为方法可以访问的类成员提供,或者简单地作为固定常量值提供。

标签: c# .net graphics jpeg


【解决方案1】:

下面是一个完整的代码示例,它将使用 GDI 绘制一个网格并放置一个十字(带有红色背景),就像下面的示例图片一样。它像其他答案一样使用 GDI,但真正的工作是通过循环遍历单元格和绘制网格线来进行的。

以下代码

byte[] bytes = CreateGridImage(10,10, 9, 9, 30);

将在 9x9 位置创建一个 10x10 的网格:

CreateGridImage() 的新增功能是添加了一个 boxSize 参数,用于设置网格中每个“正方形”的大小

public static byte[] CreateGridImage(
            int maxXCells,
            int maxYCells,
            int cellXPosition,
            int cellYPosition,
            int boxSize)
{
    using (var bmp = new System.Drawing.Bitmap(maxXCells * boxSize+1, maxYCells * boxSize+1))
    {
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.Clear(Color.Yellow);
            Pen pen = new Pen(Color.Black);
            pen.Width = 1;

            //Draw red rectangle to go behind cross
            Rectangle rect = new Rectangle(boxSize * (cellXPosition - 1), boxSize * (cellYPosition - 1), boxSize, boxSize);
            g.FillRectangle(new SolidBrush(Color.Red), rect);

            //Draw cross
            g.DrawLine(pen, boxSize * (cellXPosition - 1), boxSize * (cellYPosition - 1), boxSize * cellXPosition, boxSize * cellYPosition);
            g.DrawLine(pen, boxSize * (cellXPosition - 1), boxSize * cellYPosition, boxSize * cellXPosition, boxSize * (cellYPosition - 1));

            //Draw horizontal lines
            for (int i = 0; i <= maxXCells;i++ )
            {
                g.DrawLine(pen, (i * boxSize), 0, i * boxSize, boxSize * maxYCells);
            }

            //Draw vertical lines            
            for (int i = 0; i <= maxYCells; i++)
            {
                g.DrawLine(pen, 0, (i * boxSize), boxSize * maxXCells, i * boxSize);
            }                    
        }

        var memStream = new MemoryStream();
        bmp.Save(memStream, ImageFormat.Jpeg);
        return memStream.ToArray();
    }
}

【讨论】:

  • 太棒了!我真的没想到详细的工作。非常感谢!
  • 是的,有很多方法可以给这只猫剥皮,但上面的代码绝对可以让你开始。
  • 非常感谢您提供的详细示例 - 在类似的情况下对我很有用。
  • 谢谢。很好的例子!
【解决方案2】:

斯劳马,

这里还有另一种方式,它使用 WindowsForm 的 DataGridView 控件来绘制网格。

    public byte[] GetData()
    {
        Form form = new Form();
        //Create a new instance of DataGridView(WindowsForm) control.
        DataGridView dataGridView1 = new DataGridView();
        form.Controls.Add(dataGridView1);

        //Customize output.
        dataGridView1.RowHeadersVisible = false;
        dataGridView1.ColumnHeadersVisible = false;
        dataGridView1.ScrollBars = ScrollBars.None;
        dataGridView1.AutoSize = true;

        //Set datasource.
        dataGridView1.DataSource = GetDataTable();

        //Export as image.
        Bitmap bitmap = new Bitmap(dataGridView1.Width, dataGridView1.Height);
        dataGridView1.DrawToBitmap(bitmap, new Rectangle(Point.Empty, dataGridView1.Size));
        //bitmap.Save("sample.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);

        MemoryStream ms = new MemoryStream();
        bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);

        bitmap.Dispose();
        form.Dispose();

        return ms.ToArray();
    }

    /// <summary>
    /// Helper method.
    /// </summary>
    DataTable GetDataTable()
    {
        DataTable dt = new DataTable();

        for (int i = 0; i < 2; i++)
            dt.Columns.Add(string.Format("Column{0}", i));

        for (int i = 0; i < dt.Columns.Count; i++)
        {
            for (int j = 0; j < 10; j++)
            {
                dt.Rows.Add(new string[] { "X1", "Y1" });
            }
        }

        return dt;
    }

=== 在客户端 app.config 中(替换此行):

<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />

===

编码愉快!

【讨论】:

  • 嗯,有趣的解决方案!但我不在 Windows 窗体应用程序中,目前我没有引用任何 Windows 窗体程序集。包含这个新函数的库是 WCF 服务的一部分。您的解决方案适用于这种情况吗?
  • @Slauma,这个解决方案也适用于 WCF,我刚刚测试过。但是您必须在项目中添加对“System.Windows.Forms.dll”和“System.Drawing.dll”的引用。干杯。
【解决方案3】:
  1. 创建一个 System.Drawing.Bitmap 对象。

  2. 创建一个 Graphics 对象来进行绘图。

  3. 将位图作为 JPEG 对象保存到 MemoryStream。

不要忘记在临时位图上调用 Dispose!

示例代码如下,您可以更改像素格式和下面的各种选项,请查看 MSDN 文档。

    public static byte[] CreateGridImage(
        int maxXCells, 
        int maxYCells,
        int cellXPosition, 
        int cellYPosition)
    {
        // Specify pixel format if you like..
        using(var bmp = new System.Drawing.Bitmap(maxXCells, maxYCells)) 
        {
            using (Graphics g = Graphics.FromImage(bmp))
            {
                // Do your drawing here
            }

            var memStream = new MemoryStream();
            bmp.Save(memStream, ImageFormat.Jpeg);
            return memStream.ToArray();
        }
    }

【讨论】:

    【解决方案4】:

    首先,关于绘图,您可以:

    • 使用 Graphics 类来使用 GDI 为您提供的功能
    • 锁定位图并手动绘制

    至于保存,您可以使用MemoryStream 类来保留您的字节,然后从中获取字节数组。

    示例代码可能如下所示(假设您想使用Graphics 对象在位图上绘图:

    public byte[] CreateGridImage(int maxXCells, int maxYCells,
                        int cellXPosition, int cellYPosition)
    {
        int imageWidth = 1;
        int imageHeight = 2;
        Bitmap bmp = new Bitmap(imageWidth, imageHeight);
    
        using (Graphics g = Graphics.FromImage(bmp))
        {
            //draw code in here
        }
    
        MemoryStream imageStream = new MemoryStream();
    
        bmp.Save(imageStream, System.Drawing.Imaging.ImageFormat.Jpeg);
        bmp.Dispose();
    
        return imageStream.ToArray();
    }
    

    【讨论】:

    • 谢谢!在您的示例中,所有重要的部分都已经在一起了。要绘制,我需要 g.DrawLine 和 Pen 和 Brush 对象等函数,对吧?我不太了解您提到的两个选项(“使用图形...”与“锁定位图...”)之间的区别。这到底是什么意思?更喜欢哪个选项?
    • 是的,DrawLines 和 Pen/Brush 对象都很好,另一种选择是不使用 Graphics,而是自己操作位图的内存。如果您对 Graphics 对象给您的东西以及它的运行速度感到满意,那么您肯定不需要使用锁定位图。
    • X 和 Y 方向的单元格数可能总是
    • 在这种情况下 - 不。如果您想瞄准巨大的位图或实时/亚实时渲染(例如每秒 10 帧),那么您可以开始考虑其他选项:)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-04
    • 2017-11-03
    • 1970-01-01
    • 2013-09-12
    • 2012-07-30
    • 1970-01-01
    相关资源
    最近更新 更多