Javascript 使用Blazor(客户端)按像素绘制HTML画布图像的有效方法
目前,我有一个简单的C#类,它表示一种颜色,类似于:Javascript 使用Blazor(客户端)按像素绘制HTML画布图像的有效方法,javascript,c#,blazor-client-side,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;
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);
}
}
我正在使用C#以编程方式生成一幅尺寸为900 x 550=495000像素颜色的图像。
我只是调试了web浏览器的javascript调用,我可以看到我已经正确地发送了参数。图像也被正确渲染。
但是,从blazor填充javascript参数需要几分钟的时间
是否有任何有效的方法可以使用Blazor(客户端)逐像素地绘制画布图像?我很高兴知道您在javascript中尝试的方式很慢,因为我正在考虑尝试 我有一个名为DataJuggler.PixelDatabase的Nuget包,我正在做与您相同的事情,但使用C#代码,然后我只是将图像保存到一个新的文件名 我现在正在重构它,因为我的第一个版本使用了7吉格的内存,当时我试图保存一个包含多达2000多万项的列表 这个类在这里做的事情与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
}
这可以在几秒钟内加载并保存20个meg图像
我们应该合作,听起来我们在做同一件事。我正在考虑编写一个可以直接更新文件的Windows服务,并从blazor中删除图像处理。我试图在客户端生成一个格式为base64 PNG的图像,并将其指定为HTML img src属性:
- 使用
:创建DirectBitmap
位图时应用程序崩溃
- 使用
:创建SkiaSharp
时应用程序崩溃SKBitmap
- 使用
:应用程序生成图像ImageSharp
公共静态字符串TOBASE64图像(此画布)
{
字符串base64=null;
使用(var memoryStream=new memoryStream())
{
使用(var image=新图像(canvas.Width、canvas.Height))
{
对于(var x=0;x
blazor组件只包含:
<img src="@Base64Image" />
并且Base64Image
属性由ToBase64Image
方法填充
根据生成的图像,我会得到错误:
blazor.webassembly.js:1错误:垃圾收集器无法分配
主堆部分的16384u字节内存
奇怪的是,它似乎并不取决于图像的大小,而是取决于图像的复杂性。blazor客户端应用程序中的垃圾收集器是否可能存在不允许代码工作的问题
无论如何,它总是非常慢。首先要做的是消除Blazor并检查速度。就像用奇偶点填充那些像素,仅此而已 然后从Blazor推送数据,但仍然没有使用它 我猜你的颜色使用引用类型,这意味着获得单色JS必须跳过数组引用,然后跳过颜色引用,最后它可以获取颜色值。我建议至少将当前性能与Double数组(类似于JS图像的组织方式)进行比较
其次,您所做的一切都是前台、后台工作,然后交换图像的速度将与当前版本一样慢,但至少UI不会冻结。非常感谢您的详细回复!它似乎与Blazor服务器端配合使用。但是,我使用的是Blazor客户端,当
DirectBitmap
尝试创建新位图时,它会崩溃。我还认为我不能从Blazor客户端生成一个本地文件并在web站点中显示它。我想做的是在画布中显示一个程序生成的图像。再次感谢你的好文章。很抱歉,它不适合你。我还没有尝试Blazor客户端(Web组装)。我一直都是一个喜欢服务的人。我正在构建一个名为PixelDatabase.Net的Blazor站点,我使用一种名为BQL的语言查询图像更新,非常类似于SQL,所以它也会进行图像更新。你被客户端Blazor卡住了吗?谢谢你的评论。我的应用程序运行正常。我唯一的问题是性能,因为将495000像素的颜色从Blazor(客户端)转换为javascript似乎非常慢。你的项目看起来很棒。有像LINQ到BQL这样的东西可能会很好:)最初我加载了一个PixelInformation对象列表,您可以使用LINQ。我昨天刚刚发布了一个新版本,它不会占用太多内存。我仍然使用PixelInformation对象,但我只是不将对象保存在内存中。今天晚些时候我希望能发布这个网站。谢谢你的夸奖。这仍然是一项正在进行的工作,因为我找到了最有效的做事方式。如果你想查看我的网站,我的网站现在是live(很抱歉,如果有人认为这是垃圾邮件。我为此工作了6周)。如果说这还不算太火爆的话,那就太火爆了。Blazor是web开发人员在我看来遇到的最好的事情。
#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);
public static string ToBase64Image(this Canvas canvas)
{
string base64 = null;
using (var memoryStream = new MemoryStream())
{
using (var image = new Image<Rgba32>(canvas.Width, canvas.Height))
{
for (var x = 0; x < canvas.Width; x++)
{
for (var y = 0; y < canvas.Height; y++)
{
var c = image[1, 2];
image[x, y] = canvas[x, y].ToImageSharpColor();
}
}
image.SaveAsPng(memoryStream);
base64 = Convert.ToBase64String(memoryStream.ToArray());
}
}
return $"data:image/png;base64,{base64}";
}
<img src="@Base64Image" />