generated from gxc-solutions/gxc-template-repo
Added base rendering
This commit is contained in:
parent
63818eb2b8
commit
a100988848
7 changed files with 587 additions and 85 deletions
|
|
@ -1,51 +1,29 @@
|
|||
import { degToRad } from "@gxc-solutions/math/functions";
|
||||
import { IDrawObject, IRenderer, IScene } from "@gxc-solutions/renderer-base/interfaces";
|
||||
import { SequentialDrawThread } from "./draw-thread";
|
||||
|
||||
const drawRotatedRect = (
|
||||
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
angle: number,
|
||||
color: string,
|
||||
) => {
|
||||
context.save();
|
||||
// переносим систему координат в центр прямоугольника
|
||||
context.translate(x + width / 2, y + height / 2);
|
||||
// поворот вокруг центра
|
||||
context.rotate(degToRad(angle));
|
||||
// рисуем прямоугольник так, чтобы его центр оказался в (0,0)
|
||||
context.fillStyle = color;
|
||||
context.fillRect(-width / 2, -height / 2, width, height);
|
||||
|
||||
context.restore();
|
||||
};
|
||||
|
||||
const drawRotatedStrokeRect = (
|
||||
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
angle: number,
|
||||
color: string,
|
||||
) => {
|
||||
context.save(); // сохраняем систему координат
|
||||
context.translate(x + width / 2, y + height / 2); // переносим начало в центр фигуры
|
||||
context.rotate(degToRad(angle)); // вращаем систему координат
|
||||
context.strokeStyle = color;
|
||||
context.lineWidth = 2;
|
||||
context.strokeRect(-width / 2, -height / 2, width, height);
|
||||
context.restore(); // возвращаем всё обратно
|
||||
};
|
||||
import {
|
||||
DrawLeafs,
|
||||
IBaseFill,
|
||||
IDrawNode,
|
||||
IEllipseDrawObject,
|
||||
IImageDrawObject,
|
||||
IRectangleDrawObject,
|
||||
IRenderer,
|
||||
IScene,
|
||||
ITextDrawObject,
|
||||
} from "@gxc-solutions/renderer-base/interfaces";
|
||||
import {
|
||||
isDrawNode,
|
||||
isEllipseDrawObject,
|
||||
isImageDrawObject,
|
||||
isRectangleDrawObject,
|
||||
isTextDrawObject,
|
||||
} from "@gxc-solutions/renderer-base/utils/draw-object";
|
||||
import { Artist } from "./artist";
|
||||
import { degToRad } from "@gxc-solutions/math";
|
||||
|
||||
export class Canvas2DRenderer implements IRenderer {
|
||||
public readonly type = "2d";
|
||||
|
||||
private _context: CanvasRenderingContext2D;
|
||||
private _thread: SequentialDrawThread<(offscreenCanvas: OffscreenCanvasRenderingContext2D, objects: IDrawObject[]) => void>;
|
||||
private _artist: Artist;
|
||||
|
||||
get holder() {
|
||||
return this._canvas;
|
||||
|
|
@ -53,41 +31,103 @@ export class Canvas2DRenderer implements IRenderer {
|
|||
|
||||
constructor(private _canvas: HTMLCanvasElement) {
|
||||
this._context = this._canvas.getContext("2d");
|
||||
|
||||
this._thread = new SequentialDrawThread(
|
||||
(context2d, objects: IDrawObject[]) => {
|
||||
const canvas = context2d.canvas;
|
||||
context2d.clearRect(0, 0, canvas.width, canvas.height);
|
||||
objects.forEach((drawObject) => {
|
||||
if (drawObject.type === "rectangle-object") {
|
||||
drawRotatedRect(
|
||||
context2d,
|
||||
drawObject.x,
|
||||
drawObject.y,
|
||||
drawObject.width,
|
||||
drawObject.height,
|
||||
drawObject.angle,
|
||||
drawObject.color, // TODO в worker не знает про toString()
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
this._canvas.width,
|
||||
this._canvas.height,
|
||||
[drawRotatedRect, drawRotatedStrokeRect, degToRad],
|
||||
);
|
||||
this._artist = new Artist(this._context);
|
||||
}
|
||||
|
||||
public async render(scene: IScene): Promise<void> {
|
||||
const bitmap = await this._thread.run([scene.objects]);
|
||||
this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
this.renderBg(scene.background);
|
||||
scene.static.forEach((leaf) => this.drawLeaf(leaf));
|
||||
this.renderNode(scene.node);
|
||||
this.renderNode(scene.selection);
|
||||
}
|
||||
|
||||
const prev = this._context.globalCompositeOperation;
|
||||
this._context.globalCompositeOperation = "copy";
|
||||
// Важно: убедись, что размеры совпадают
|
||||
this._context.drawImage(bitmap, 0, 0, this._canvas.width, this._canvas.height);
|
||||
this._context.globalCompositeOperation = prev;
|
||||
renderBg(fill: IBaseFill) {
|
||||
this._context.save();
|
||||
this._artist.setFill(fill);
|
||||
this._context.fillRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
this._context.restore();
|
||||
}
|
||||
|
||||
// Освобождаем ресурсы
|
||||
if ("close" in bitmap) bitmap.close();
|
||||
renderNode(drawNode: IDrawNode) {
|
||||
drawNode.children.forEach((leaf) => {
|
||||
if (isDrawNode(leaf)) {
|
||||
this._artist.setTransform(leaf.transform);
|
||||
this._context.save();
|
||||
this.renderNode(leaf);
|
||||
this._context.restore();
|
||||
}
|
||||
this.drawLeaf(leaf as DrawLeafs);
|
||||
});
|
||||
}
|
||||
|
||||
private drawLeaf(leaf: DrawLeafs) {
|
||||
if (isEllipseDrawObject(leaf)) {
|
||||
this._context.save();
|
||||
const dn = leaf as IEllipseDrawObject;
|
||||
this._context.globalAlpha = dn.opacity;
|
||||
this._context.globalCompositeOperation = dn.blendMode as GlobalCompositeOperation;
|
||||
this._artist.setTransform(dn.transform);
|
||||
this._artist.setFill(dn.fill);
|
||||
this._context.ellipse(
|
||||
dn.ellipse.center.x - dn.ellipse.radiusX,
|
||||
dn.ellipse.center.y - dn.ellipse.radiusY,
|
||||
dn.ellipse.radiusX,
|
||||
dn.ellipse.radiusY,
|
||||
degToRad(0),
|
||||
0,
|
||||
degToRad(360),
|
||||
);
|
||||
this._context.fill();
|
||||
this._artist.setStroke(dn.stroke);
|
||||
if (dn.stroke.width > 0) {
|
||||
this._context.stroke();
|
||||
}
|
||||
this._context.restore();
|
||||
}
|
||||
if (isImageDrawObject(leaf)) {
|
||||
this._context.save();
|
||||
const dn = leaf as IImageDrawObject;
|
||||
this._context.globalAlpha = dn.opacity;
|
||||
this._context.globalCompositeOperation = dn.blendMode as GlobalCompositeOperation;
|
||||
this._artist.setTransform(dn.transform);
|
||||
|
||||
this._artist.drawImage(
|
||||
dn.source,
|
||||
{ x: dn.rectangle.x, y: dn.rectangle.y },
|
||||
{ width: dn.rectangle.width, height: dn.rectangle.height },
|
||||
);
|
||||
this._context.restore();
|
||||
}
|
||||
if (isRectangleDrawObject(leaf)) {
|
||||
this._context.save();
|
||||
const dn = leaf as IRectangleDrawObject;
|
||||
this._context.globalAlpha = dn.opacity;
|
||||
this._context.globalCompositeOperation = dn.blendMode as GlobalCompositeOperation;
|
||||
this._artist.setTransform(dn.transform);
|
||||
this._artist.setFill(dn.fill);
|
||||
this._context.fillRect(dn.rectangle.x, dn.rectangle.y, dn.rectangle.width, dn.rectangle.height);
|
||||
this._artist.setStroke(dn.stroke);
|
||||
if (dn.stroke.width > 0) {
|
||||
this._context.strokeRect(dn.rectangle.x, dn.rectangle.y, dn.rectangle.width, dn.rectangle.height);
|
||||
}
|
||||
this._context.restore();
|
||||
}
|
||||
if (isTextDrawObject(leaf)) {
|
||||
this._context.save();
|
||||
const dn = leaf as ITextDrawObject;
|
||||
this._context.globalAlpha = dn.opacity;
|
||||
this._context.globalCompositeOperation = dn.blendMode as GlobalCompositeOperation;
|
||||
this._artist.setTransform(dn.transform);
|
||||
this._artist.setStroke(dn.textStroke);
|
||||
this._artist.setFont(dn.font);
|
||||
const metrics = this._context.measureText(dn.text);
|
||||
console.warn(metrics);
|
||||
if (dn.textStroke.width > 0) {
|
||||
this._context.strokeText(dn.text, dn.rectangle.x, dn.rectangle.y);
|
||||
}
|
||||
this._context.fillText(dn.text, dn.rectangle.x, dn.rectangle.y);
|
||||
this._context.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue