-
- using System;
- using System.Collections.Generic;
- using Godot;
-
- public partial class ImageCanvas : Sprite2D, IDestroy
- {
- /// <summary>
- /// 画布大小
- /// </summary>
- public int Width { get; }
-
- /// <summary>
- /// 画布高度
- /// </summary>
- public int Height { get; }
-
- public bool IsDestroyed { get; private set; }
-
- private Image _canvas;
- private ImageTexture _texture;
-
- public ImageCanvas(int width, int height)
- {
- Width = width;
- Height = height;
-
- _canvas = Image.Create(width, height, false, Image.Format.Rgba8);
- _texture = ImageTexture.CreateFromImage(_canvas);
-
- _canvas.Fill(Colors.Gray);
- var w = _canvas.GetWidth();
- var h = _canvas.GetHeight();
- for (int i = 0; i < w; i++)
- {
- _canvas.SetPixel(i, h / 2, Colors.Green);
- }
-
- for (int j = 0; j < h; j++)
- {
- _canvas.SetPixel(w / 2, j, Colors.Green);
- }
- }
-
- public override void _Ready()
- {
- Centered = false;
- Texture = _texture;
- }
-
- /// <summary>
- /// 添加到预渲染队列中
- /// </summary>
- /// <param name="texture">需要渲染的纹理</param>
- /// <param name="x">离画布左上角x坐标</param>
- /// <param name="y">离画布左上角y坐标</param>
- /// <param name="angle">旋转角度, 角度制</param>
- /// <param name="centerX">旋转中心点x</param>
- /// <param name="centerY">旋转中心点y</param>
- /// <param name="flipY">是否翻转y轴</param>
- public void DrawImageInCanvas(Texture2D texture, int x, int y, float angle, int centerX, int centerY, bool flipY)
- {
- var item = new EnqueueItem();
- item.Canvas = this;
- item.Image = texture.GetImage();
- item.X = x;
- item.Y = y;
- item.Angle = angle;
- item.CenterX = centerX;
- item.CenterY = centerY;
- item.FlipY = flipY;
-
- _enqueueItems.Enqueue(item);
- }
-
- public void Destroy()
- {
- if (IsDestroyed)
- {
- return;
- }
-
- IsDestroyed = true;
- }
-
- private void Redraw()
- {
- _texture.Update(_canvas);
- }
-
- private void HandlerDrawImageInCanvas(EnqueueItem item)
- {
- var image = item.Image;
- var newAngle = Mathf.RoundToInt(Utils.ConvertAngle(item.Angle));
-
- if (newAngle == 0) //原图, 直接画上去
- {
- if (item.FlipY)
- {
- image.FlipY();
- }
-
- if (image.GetFormat() != Image.Format.Rgba8)
- {
- image.Convert(Image.Format.Rgba8);
- }
- _canvas.BlendRect(image, new Rect2I(0, 0, image.GetWidth(), image.GetHeight()),
- new Vector2I(item.X - item.CenterX, item.Y - item.CenterY));
- }
- else //其他角度
- {
- if (newAngle <= 180)
- {
- if (item.FlipY)
- {
- image.FlipY();
- }
-
- DrawRotateImage(image, item.X, item.Y, newAngle, item.CenterX, item.CenterY);
- }
- else
- {
- image.FlipX();
- if (!item.FlipY)
- {
- image.FlipY();
- }
-
- DrawRotateImage(
- image, item.X, item.Y,
- newAngle - 180,
- image.GetWidth() - item.CenterX - 1, image.GetHeight() - item.CenterY - 1
- );
- }
- }
-
- image.Dispose();
- item.Image = null;
- }
-
- private void DrawRotateImage(Image origin, int x, int y, int angle, int centerX, int centerY)
- {
- var rotation = Mathf.DegToRad(angle);
- var width = origin.GetWidth();
- var height = origin.GetHeight();
-
- var cosAngle = Mathf.Cos(rotation);
- var sinAngle = Mathf.Sin(rotation);
- if (cosAngle == 0)
- {
- cosAngle = -1e-6f;
- }
-
- if (sinAngle == 0)
- {
- sinAngle = 1e-6f;
- }
-
- var newWidth = Mathf.RoundToInt(width * Mathf.Abs(cosAngle) + height * sinAngle);
- var newHeight = Mathf.RoundToInt(width * sinAngle + height * Mathf.Abs(cosAngle));
-
- var offsetX =
- Mathf.RoundToInt((centerX / sinAngle +
- (0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height) /
- cosAngle -
- (-0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width) /
- sinAngle - centerY / cosAngle) /
- (cosAngle / sinAngle + sinAngle / cosAngle));
-
-
- var offsetY =
- Mathf.RoundToInt((centerX / cosAngle -
- (-0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width) /
- cosAngle -
- (0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height) /
- sinAngle + centerY / sinAngle) /
- (sinAngle / cosAngle + cosAngle / sinAngle));
-
- var num1 = -0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width;
- var num2 = 0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height;
-
- var cw = _canvas.GetWidth();
- var ch = _canvas.GetHeight();
-
- var numX = Mathf.Min(num1, (newWidth - 1) * cosAngle + (newHeight - 1) * sinAngle + num1);
- var numY = Mathf.Min(num2, -(newWidth - 1) * sinAngle + (newHeight - 1) * cosAngle + num2);
-
- for (var x2 = 0; x2 < newWidth; x2++)
- {
- for (var y2 = 0; y2 < newHeight; y2++)
- {
- //映射到原图的位置
- // var x1 = x2 * cosAngle + y2 * sinAngle + num1;
- // var y1 = -x2 * sinAngle + y2 * cosAngle + num2;
-
-
- var x1 = Mathf.FloorToInt(x2 * cosAngle + y2 * sinAngle + num1 - numX);
- var y1 = Mathf.FloorToInt(-x2 * sinAngle + y2 * cosAngle + num2 - numY);
-
- if (x1 < 0 || x1 >= width || y1 < 0 || y1 >= height)
- {
- //在图片外
- continue;
- }
-
-
- var cx = x2 + x - offsetX;
- if (cx < 0 || cx >= cw)
- {
- continue;
- }
-
- var cy = y2 + y - offsetY;
- if (cy < 0 || cy >= ch)
- {
- continue;
- }
-
- //_canvas.SetPixel(cx, cy, origin.GetPixel(x1, y1));
- _canvas.SetPixel(cx, cy, _canvas.GetPixel(cx, cy).Blend(origin.GetPixel(
- //Mathf.RoundToInt(x1), Mathf.RoundToInt(y1)
- x1, y1
- )));
- }
- }
- }
-
- //--------------------------------------------------------------------------------------------------------------
-
- private class EnqueueItem
- {
- public ImageCanvas Canvas;
- public Image Image;
- public int X;
- public int Y;
- public float Angle;
- public int CenterX;
- public int CenterY;
- public bool FlipY;
- }
-
- /// <summary>
- /// 同一帧下将队列里的image绘制到指定画布下最大消耗时间, 如果绘制的时间超过了这个值, 则队列中后面的image将会放到下一帧绘制
- /// </summary>
- public static float MaxHandlerTime { get; set; } = 2f;
-
- private static readonly Queue<EnqueueItem> _enqueueItems = new Queue<EnqueueItem>();
- private static readonly HashSet<ImageCanvas> _redrawCanvas = new HashSet<ImageCanvas>();
-
- /// <summary>
- /// 根据重绘队列更新画布
- /// </summary>
- public static void UpdateImageCanvas(float delta)
- {
- if (_enqueueItems.Count > 0)
- {
- _redrawCanvas.Clear();
- var startTime = DateTime.Now;
-
- var c = _enqueueItems.Count;
- //执行绘制操作
- //绘制的总时间不能超过MaxHandlerTime, 如果超过了, 那就下一帧再画其它的吧
- do
- {
- var item = _enqueueItems.Dequeue();
- if (!item.Canvas.IsDestroyed)
- {
- item.Canvas.HandlerDrawImageInCanvas(item);
- _redrawCanvas.Add(item.Canvas);
- }
- } while (_enqueueItems.Count > 0 && (DateTime.Now - startTime).TotalMilliseconds < MaxHandlerTime);
-
- if (_enqueueItems.Count > 0)
- {
- GD.Print($"ImageCanvas: 还剩下{_enqueueItems.Count}个, 已经处理了{c - _enqueueItems.Count}个, 重绘画布{_redrawCanvas.Count}张");
- }
- else
- {
- GD.Print($"ImageCanvas: 全部画完, 已经处理了{c - _enqueueItems.Count}个, 重绘画布{_redrawCanvas.Count}张");
- }
-
- //重绘画布
- foreach (var drawCanvas in _redrawCanvas)
- {
- drawCanvas.Redraw();
- }
-
- _redrawCanvas.Clear();
- }
- }
- }