javascript - 使用 Blazor(客户端)按像素绘制 HTML Canvas 图像的有效方法

标签 javascript c# blazor-client-side

目前我只是有一个代表颜色的 C# 类,类似这样:

public class Color
{
        public double Red { get; }
        public double Green { get; }
        public double Blue { get; }

        public Color(double red, double green, double blue)
        {
            Red = red;
            Green = green;
            Blue = blue;
        }
}

还有一个绘制图像的 JavaScript 方法:

window.canvas = {
    render: (canvas, width, height, colors) => {
        canvas.width = width;
        canvas.height = height;

        let context = canvas.getContext("2d");
        let imageData = context.getImageData(0, 0, canvas.width, canvas.height);
        let data = imageData.data;

        let length = width * height;
        for (let i = 0; i < length; i++) {
            let dataIndex = i * 4;
            data[dataIndex] = colors[i].red;
            data[dataIndex + 1] = colors[i].green;
            data[dataIndex + 2] = colors[i].blue;
            data[dataIndex + 3] = 255;
        }

        context.putImageData(imageData, 0, 0);
    }
}

我从 Blazor 调用该方法(其中 dataColor 数组):

await JSRuntime.InvokeAsync<object>("canvas.render", new object[] { CanvasElement, canvas.Width, canvas.Height, data });

我正在使用 C# 以编程方式生成图像,尺寸为 900 x 550 = 495000 像素颜色。 我可以使用网络浏览器调试 javascript 调用,并且可以看到我已正确发送参数。图像也正确渲染。 但是,从 blazor 填充 JavaScript 参数需要几分钟的时间。

是否有使用 Blazor(客户端)逐像素绘制 Canvas 图像的有效方法?

最佳答案

我很高兴知道你在 javascript 中尝试的方式很慢,因为我正在考虑尝试这一点。

我有一个名为 DataJuggler.PixelDatabase 的 Nuget 包,我正在做与您相同的事情,但使用的是 C# 代码,然后我只需将图像保存到新的文件名。

https://github.com/DataJuggler/PixelDatabase

我现在正在重构它,因为当我试图保存一个包含多达 20+ 百万个项目的列表时,我的第一个版本使用了 7 GB 内存。

这个类与 JavaScript 执行相同的操作。几年前有人在这里发布过,我希望我保留了他们的信息以给予他们信任。

#region using statements

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

#endregion

namespace DataJuggler.PixelDatabase
{

    #region class DirectBitmap
    /// <summary>
    /// This class is used as a faster alternative to GetPixel and SetPixel
    /// </summary>
    public class DirectBitmap : IDisposable
    {

        #region Constructor
        /// <summary>
        /// Create a new instance of a 'DirectBitmap' object.
        /// </summary>
        public DirectBitmap(int width, int height)
        {
            Width = width;
            Height = height;
            Bits = new Int32[width * height];
            BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
            Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
        }
        #endregion

        #region Methods

            #region Dispose()
            /// <summary>
            /// method Dispose
            /// </summary>
            public void Dispose()
            {
                if (Disposed) return;
                Disposed = true;
                Bitmap.Dispose();
                BitsHandle.Free();
            }
            #endregion

            #region GetPixel(int x, int y)
            /// <summary>
            /// method Get Pixel
            /// </summary>
            public Color GetPixel(int x, int y)
            {
                int index = x + (y * Width);
                int col = Bits[index];
                Color result = Color.FromArgb(col);

                return result;
            }
            #endregion

            #region SetPixel(int x, int y, Color color)
            /// <summary>
            /// method Set Pixel
            /// </summary>
            public void SetPixel(int x, int y, Color color)
            {
                int index = x + (y * Width);
                int col = color.ToArgb();

                Bits[index] = col;
            }
            #endregion

        #endregion

        #region Properties

            #region Bitmap
            /// <summary>
            /// method [Enter Method Description]
            /// </summary>
            public Bitmap Bitmap { get; private set; }
            #endregion

            #region Bits
            /// <summary>
            /// method [Enter Method Description]
            /// </summary>
            public Int32[] Bits { get; private set; }
            #endregion

            #region BitsHandle
            /// <summary>
            /// This is a ptr to the garbage collector
            /// </summary>
            protected GCHandle BitsHandle { get; private set; }
            #endregion

            #region Disposed
            /// <summary>
            /// method [Enter Method Description]
            /// </summary>
            public bool Disposed { get; private set; }
            #endregion

            #region Height
            /// <summary>
            /// method [Enter Method Description]
            /// </summary>
            public int Height { get; private set; }
            #endregion

            #region Width
            /// <summary>
            /// method [Enter Method Description]
            /// </summary>
            public int Width { get; private set; }
            #endregion

        #endregion

    }
    #endregion

}

那么这个类这里有一个如何加载DirectBitmap的例子。 其中一些是我的应用程序特有的,但您可以取出您需要的内容。

#region using statements

using DataJuggler.UltimateHelper.Core;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

#endregion

namespace DataJuggler.PixelDatabase
{

    #region class PixelDatabaseLoader
    /// <summary>
    /// This class is used to load PixelDatabases and their DirectBitmaps
    /// </summary>
    public class PixelDatabaseLoader
    {

        #region Methods

            #region LoadPixelDatabase(Image original, StatusUpdate updateCallback)
            /// <summary>
            /// This method is used to load a PixelDatabase and its DirectBitmap object.
            /// </summary>
            /// <param name="bitmap"></param>
            /// <returns></returns>
            public static PixelDatabase LoadPixelDatabase(Image original, StatusUpdate updateCallback)
            {
                // initial valule
                PixelDatabase pixelDatabase = null;

                try
                {
                    // convert to a bitmap
                    Bitmap bitmap = (Bitmap) original;

                    pixelDatabase = LoadPixelDatabase(bitmap, updateCallback);
                }
                catch (Exception error)
                {
                    // write to console for now
                    DebugHelper.WriteDebugError("LoadPixelDatabase", "PixelDatabaseLoader", error);
                }
                finally
                {

                }

                // return value
                return pixelDatabase;
            }
            #endregion

            #region LoadPixelDatabase(string imagePath, StatusUpdate updateCallback)
            /// <summary>
            /// This method is used to load a PixelDatabase and its DirectBitmap object from an imagePath
            /// </summary>
            /// <param name="bitmap"></param>
            /// <returns></returns>
            public static PixelDatabase LoadPixelDatabase(string imagePath, StatusUpdate updateCallback)
            {
                // initial valule
                PixelDatabase pixelDatabase = null;

                try
                {
                    // if we have an imagePath
                    if (TextHelper.Exists(imagePath))
                    { 
                        // create the Bitmap
                        using (Bitmap bitmap = (Bitmap) Bitmap.FromFile(imagePath))
                        {
                            // load the pixelDatabase
                            pixelDatabase = LoadPixelDatabase(bitmap, updateCallback);
                        }
                    }   
                }
                catch (Exception error)
                {
                    // write to console for now
                    DebugHelper.WriteDebugError("LoadPixelDatabase", "PixelDatabaseLoader", error);
                }
                finally
                {

                }

                // return value
                return pixelDatabase;
            }
            #endregion

            #region LoadPixelDatabase(Bitmap original, StatusUpdate updateCallback)
            /// <summary>
            /// This method is used to load a PixelDatabase and its DirectBitmap object.
            /// </summary>
            /// <param name="bitmap"></param>
            /// <returns></returns>
            public static PixelDatabase LoadPixelDatabase(Bitmap original, StatusUpdate updateCallback)
            {
                // initial valule
                PixelDatabase pixelDatabase = null;

                // locals
                int max = 0;

                try
                {
                    // if we have an image
                    if (NullHelper.Exists(original))
                    { 
                        // create a new bitmap
                        using (Bitmap source = new Bitmap(original))
                        {
                             // Create a new instance of a 'PixelDatabase' object.
                            pixelDatabase = new PixelDatabase();

                            // Create a DirectBitmap
                            pixelDatabase.DirectBitmap = new DirectBitmap(source.Width, source.Height);

                            // Code To Lockbits
                            BitmapData bitmapData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadWrite, source.PixelFormat);
                            IntPtr pointer = bitmapData.Scan0;
                            int size = Math.Abs(bitmapData.Stride) * source.Height;
                            byte[] pixels = new byte[size];
                            Marshal.Copy(pointer, pixels, 0, size);

                            // End Code To Lockbits

                            // Marshal.Copy(pixels,0,pointer, size);
                            source.UnlockBits(bitmapData);

                            // locals
                            Color color = Color.FromArgb(0, 0, 0);
                            int red = 0;
                            int green = 0;
                            int blue = 0;
                            int alpha = 0;

                            // variables to hold height and width
                            int width = source.Width;
                            int height = source.Height;
                            int x = -1;
                            int y = 0;

                            // if the UpdateCallback exists
                            if (NullHelper.Exists(updateCallback))
                            {
                                // Set the value for max
                                max = height * width;    

                                // Set the graph max
                                updateCallback("SetGraphMax", max);
                            }

                            // Iterating the pixel array, every 4th byte is a new pixel, much faster than GetPixel
                            for (int a = 0; a < pixels.Length; a = a + 4)
                            {
                                // increment the value for x
                                x++;

                                // every new column
                                if (x >= width)
                                {
                                    // reset x
                                    x = 0;

                                    // Increment the value for y
                                    y++;
                                }      

                                // get the values for r, g, and blue
                                blue = pixels[a];
                                green = pixels[a + 1];
                                red = pixels[a + 2];
                                alpha = pixels[a + 3];

                                // create a color
                                color = Color.FromArgb(alpha, red, green, blue);

                                // Set the pixel at this spot
                                pixelDatabase.DirectBitmap.SetPixel(x, y, color);
                            }
                        }

                        // Create the MaskManager 
                        pixelDatabase.MaskManager = new MaskManager();
                    }
                }
                catch (Exception error)
                {
                    // write to console for now
                    DebugHelper.WriteDebugError("LoadPixelDatabase", "PixelDatabaseLoader", error);
                }

                // return value
                return pixelDatabase;
            }
            #endregion

        #endregion

    }
    #endregion

}

因此,在加载图像并应用更改后,我会像这样更新保存:

// get the bitmap
Bitmap bitmap = PixelDatabase.DirectBitmap.Bitmap;

// Get a fileInfo of the oldPath
FileInfo fileInfo = new FileInfo(FullImagePath);

// Get the index of the period
int index = fileInfo.Name.IndexOf(".");

// get the name
string name = fileInfo.Name.Substring(0, index);

// Get the directory
DirectoryInfo directory = fileInfo.Directory;

// get the directoryFullname
string fullPath = directory.FullName;

// newFileName
string newFileName = name + "." + Guid.NewGuid().ToString().Substring(0, 12) + ".png";

// Get the newPath
string newPath = Path.Combine(fullPath, newFileName);

// Save
bitmap.Save(newPath, ImageFormat.Png);

这可以在几秒钟内加载并保存 20 兆图像。

我们应该合作,听起来我们正在做同一件事。我正在考虑编写一个可以直接更新文件的 Windows 服务,并从 blazor 中删除图像处理。

关于javascript - 使用 Blazor(客户端)按像素绘制 HTML Canvas 图像的有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61321942/

相关文章:

javascript - Webpack 不会从 node_modules 导入包(仅限 js)

javascript - 如果 div 边距设置为自动,如何获得以像素为单位的 div 左右边距?

javascript - 如何并行执行两个异步函数,然后等待它们完成?

c# - 获取 https://www.google-analytics.com/ga.js net::ERR_TIMED_OUT

javascript - 在 JQuery 中添加变换 (rotateZ) 和左动画

c# - linq查询将3个表连接到一个表上

c# - CXF Web 服务的 .NET 客户端身份验证和 SOAP 凭据 header

asp.net-core - Blazor 布局中的顶部菜单

amazon-cognito - 如何将 OpenID 身份验证从 Blazor WebAssembly 传递到 .NET Core WebApi 后端,两者都使用 Cognito 作为 OpenID 提供程序?

.net - Blazor WebAssembly。在布局级别添加授权属性