Overte C++ Documentation
CanvasCommand.h
1 //
2 // CanvasCommand.h
3 // libraries/script-engine/src
4 //
5 // Created by Ada <ada@thingvellir.net> on 2025-02-27
6 // Copyright 2025 Overte e.V.
7 //
8 // Distributed under the Apache License, Version 2.0.
9 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10 // SPDX-License-Identifier: Apache-2.0
11 //
12 
15 
16 #ifndef hifi_CanvasCommand_h
17 #define hifi_CanvasCommand_h
18 
19 #include "ScriptValue.h"
20 #include "ScriptValueUtils.h"
21 #include "Scriptable.h"
22 
23 #include <QPainter>
24 #include <QPainterPath>
25 
26 class ScriptEngine;
27 
28 /*@jsdoc
29  * @typedef {object} CanvasImage
30  * @property {ArrayBuffer} data - RGBA8 pixel data
31  * @property {number} width - Image width in pixels
32  * @property {number} height - Image height in pixels
33  */
34 struct CanvasImage {
35  // TODO: TypedArray reference
36  QByteArray buffer;
37 
38  int width, height;
39  bool _ownsData; // true if buffer is valid, false if using TypedArray
40 
41  CanvasImage() : buffer(QByteArray()), width(0), height(0), _ownsData(true) {}
42  CanvasImage(QByteArray buffer, int width, int height) : buffer(buffer), width(width), height(height), _ownsData(true) {}
43 };
44 
45 /*@jsdoc
46  * @typedef {object} CanvasPathElement
47  * @property {number} type - 0: Move to, 1: Line to, 2: Cubic curve to
48  * @property {number} x
49  * @property {number} y
50  * @property {number} c1x
51  * @property {number} c1y
52  * @property {number} c2x
53  * @property {number} c2y
54  */
55 struct CanvasPathElement {
56  int type;
57  qreal x, y, c1x, c1y, c2x, c2y;
58 
59  CanvasPathElement(int type, qreal x, qreal y) : type(type), x(x), y(y), c1x(0), c1y(0), c2x(0), c2y(0) {}
60  CanvasPathElement(int type, qreal x, qreal y, qreal c1x, qreal c1y, qreal c2x, qreal c2y) : type(type), x(x), y(y), c1x(c1x), c1y(c1y), c2x(c2x), c2y(c2y) {}
61  CanvasPathElement() : type(0), x(0), y(0), c1x(0), c1y(0), c2x(0), c2y(0) {}
62 };
63 
64 /*@jsdoc
65  * @namespace CanvasCommand
66  *
67  * @hifi-interface
68  * @hifi-client-entity
69  *
70  * @readonly
71  * @property {number} HINT_NO_ANTIALIASING=1 - If set, drawn shapes will have pixelated edges.
72  * @property {number} HINT_NO_TEXT_ANTIALIASING=2 - If set, text will have pixelated edges.
73  * @property {number} HINT_NEAREST_SCALING=4 - If set, drawn images will be scaled using pixelated, nearest-neighbor scaling.
74  *
75  * @property {number} TEXT_ALIGN_LEFT=1 - Align drawn text to the left side of its bounding box.
76  * @property {number} TEXT_ALIGN_RIGHT=2 - Align drawn text to the right side of its bounding box.
77  * @property {number} TEXT_ALIGN_HCENTER=4 - Align drawn text horizontally to the center of its bounding box.
78  * @property {number} TEXT_ALIGN_JUSTIFY=8 - Justifies text horizontally to fit in its bounding box.
79  * @property {number} TEXT_ALIGN_TOP=32 - Align drawn text to the top of its bounding box.
80  * @property {number} TEXT_ALIGN_BOTTOM=64 - Align drawn text to the bottom of its bounding box.
81  * @property {number} TEXT_ALIGN_VCENTER=32 - Align drawn text vertically to the center of its bounding box.
82  * @property {number} TEXT_ALIGN_CENTER=132 - Align drawn text to the center of its bounding box, both horizontally and vertically.
83  *
84  * @property {number} BLEND_SOURCEOVER=0
85  * @property {number} BLEND_DESTINATIONOVER=1
86  * @property {number} BLEND_CLEAR=2
87  * @property {number} BLEND_SOURCE=3
88  * @property {number} BLEND_DESTINATION=4
89  * @property {number} BLEND_SOURCEIN=5
90  * @property {number} BLEND_DESTINATIONIN=6
91  * @property {number} BLEND_SOURCEOUT=7
92  * @property {number} BLEND_DESTINATIONOUT=8
93  * @property {number} BLEND_SOURCEATOP=9
94  * @property {number} BLEND_DESTINATIONATOP=10
95  * @property {number} BLEND_XOR=11
96  * @property {number} BLEND_PLUS=12
97  * @property {number} BLEND_MULTIPLY=13
98  * @property {number} BLEND_SCREEN=14
99  * @property {number} BLEND_OVERLAY=15
100  * @property {number} BLEND_DARKEN=16
101  * @property {number} BLEND_LIGHTEN=17
102  * @property {number} BLEND_COLORDODGE=18
103  * @property {number} BLEND_COLORBURN=19
104  * @property {number} BLEND_HARDLIGHT=20
105  * @property {number} BLEND_SOFTLIGHT=21
106  * @property {number} BLEND_DIFFERENCE=22
107  * @property {number} BLEND_EXCLUSION=23
108  *
109  * @example <caption>Create a canvas entity and draw "Hello, world!" into it as text.</caption>
110  * const CanvasCommand = Script.require("canvasCommand");
111  *
112  * const canvas = Entities.addEntity({
113  * type: "Canvas",
114  * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -1 })),
115  * dimensions: { x: 1, y: 0.5, z: 0.01 },
116  * lifetime: 30, // Delete after 30 seconds.
117  * width: 256,
118  * height: 128,
119  * unlit: true,
120  * transparent: true,
121  * }, "local");
122  *
123  * Entities.canvasPushCommands(canvas, [
124  * CanvasCommand.color([255, 255, 255, 255]),
125  * CanvasCommand.font("sans-serif", 20),
126  * CanvasCommand.fillText(
127  * "Hello, world!",
128  * 0, 0,
129  * 256, 128,
130  * CanvasCommand.TEXT_ALIGN_CENTER
131  * ),
132  * ]);
133  *
134  * Entities.canvasCommit(canvas);
135  * @example <caption>Create a canvas entity and draw an XOR pattern into it.</caption>
136  * const CanvasCommand = Script.require("canvasCommand");
137  *
138  * const canvas = Entities.addEntity({
139  * type: "Canvas",
140  * position: Vec3.sum(MyAvatar.getHeadPosition(), Vec3.multiplyQbyV(MyAvatar.orientation, [0, 0, -2])),
141  * rotation: MyAvatar.orientation,
142  * dimensions: [2, 2, 0.1],
143  * width: 256,
144  * height: 256,
145  * unlit: true,
146  * collisionless: true,
147  * }, "local");
148  *
149  * const buffer = new Uint8Array(256 * 256 * 4);
150  * const img = {data: buffer.buffer, width: 256, height: 256};
151  *
152  * for (let x = 0; x < 256; x++) {
153  * for (let y = 0; y < 256; y++) {
154  * let color = x ^ y;
155  * buffer[(y * 256 * 4) + (x * 4) + 0] = color;
156  * buffer[(y * 256 * 4) + (x * 4) + 1] = color;
157  * buffer[(y * 256 * 4) + (x * 4) + 2] = color;
158  * buffer[(y * 256 * 4) + (x * 4) + 3] = 255;
159  * }
160  * }
161  *
162  * Entities.canvasPushPixels(canvas, img);
163  * Entities.canvasCommit(canvas);
164  *
165  * Script.scriptEnding.connect(() => Entities.deleteEntity(canvas));
166  *
167  * // delete after 10 seconds
168  * Script.setTimeout(() => Script.stop(), 1000 * 10);
169  */
170 
171 /*@jsdoc
172  * Sets the line width for stroked shapes.
173  * @name CanvasCommand.strokeWidth
174  * @function
175  * @param {number} width - Stroke width in pixels.
176  * @returns {object}
177  */
178 
179 /*@jsdoc
180  * Sets the stroke and fill color to use.
181  * @name CanvasCommand.color
182  * @function
183  * @param {Color|Vec4} color - Stroke and fill color to use.
184  * @returns {object}
185  */
186 
187 /*@jsdoc
188  * Sets rendering hints.
189  * @name CanvasCommand.hints
190  * @function
191  * @param {number} hints
192  * @returns {object}
193  */
194 
195 /*@jsdoc
196  * Sets the blending mode.
197  * @name CanvasCommand.blendMode
198  * @function
199  * @param {number} mode
200  * @returns {object}
201  */
202 
203 /*@jsdoc
204  * Sets the font to use for {@link CanvasCommand.fillText}.
205  * @name CanvasCommand.font
206  * @function
207  * @param {string} family
208  * @param {number} size=16 - Pixel height
209  * @param {number} weight=400 - 400 is normal, 700 is bold
210  * @param {boolean} italic=false
211  * @returns {object}
212  */
213 
214 /*@jsdoc
215  * Clears a region of a canvas with transparent black.
216  * @name CanvasCommand.clearRect
217  * @function
218  * @param {number} x - Left of the rectangle.
219  * @param {number} x - Top of the rectangle.
220  * @param {number} w - Width of the rectangle.
221  * @param {number} h - Height of the rectangle.
222  * @returns {object}
223  */
224 
225 /*@jsdoc
226  * @name CanvasCommand.fillPath
227  * @function
228  * @param {CanvasPathElement[]} path
229  * @returns {object}
230  */
231 
232 /*@jsdoc
233  * @name CanvasCommand.fillRect
234  * @function
235  * @param {number} x - Left of the rectangle.
236  * @param {number} x - Top of the rectangle.
237  * @param {number} w - Width of the rectangle.
238  * @param {number} h - Height of the rectangle.
239  * @returns {object}
240  */
241 
242 /*@jsdoc
243  * @name CanvasCommand.fillEllipse
244  * @function
245  * @param {number} x - Left of the rectangle.
246  * @param {number} x - Top of the rectangle.
247  * @param {number} w - Width of the rectangle.
248  * @param {number} h - Height of the rectangle.
249  * @returns {object}
250  */
251 
252 /*@jsdoc
253  * @name CanvasCommand.fillText
254  * @function
255  * @param {string} text
256  * @param {number} x - Left of the rectangle.
257  * @param {number} x - Top of the rectangle.
258  * @param {number} w - Width of the rectangle.
259  * @param {number} h - Height of the rectangle.
260  * @param {number} flags
261  * @returns {object}
262  */
263 
264 /*@jsdoc
265  * @name CanvasCommand.strokePath
266  * @function
267  * @param {CanvasPathElement[]} path
268  * @returns {object}
269  */
270 
271 /*@jsdoc
272  * @name CanvasCommand.strokeRect
273  * @function
274  * @param {number} x - Left of the rectangle.
275  * @param {number} x - Top of the rectangle.
276  * @param {number} w - Width of the rectangle.
277  * @param {number} h - Height of the rectangle.
278  * @returns {object}
279  */
280 
281 /*@jsdoc
282  * @name CanvasCommand.strokeArc
283  * @function
284  * @param {number} x - Left of the rectangle.
285  * @param {number} x - Top of the rectangle.
286  * @param {number} w - Width of the rectangle.
287  * @param {number} h - Height of the rectangle.
288  * @param {number} startAngle
289  * @param {number} spanAngle
290  * @returns {object}
291  */
292 
293 /*@jsdoc
294  * @name CanvasCommand.strokeEllipse
295  * @function
296  * @param {number} x - Left of the rectangle.
297  * @param {number} x - Top of the rectangle.
298  * @param {number} w - Width of the rectangle.
299  * @param {number} h - Height of the rectangle.
300  * @returns {object}
301  */
302 
303 /*@jsdoc
304  * @name CanvasCommand.point
305  * @function
306  * @param {number} x
307  * @param {number} y
308  * @returns {object}
309  */
310 
311 /*@jsdoc
312  * @name CanvasCommand.line
313  * @function
314  * @param {number} x1
315  * @param {number} y1
316  * @param {number} x2
317  * @param {number} y2
318  * @returns {object}
319  */
320 
321 /*@jsdoc
322  * @name CanvasCommand.imageCopy
323  * @function
324  * @param {CanvasImage} image
325  * @param {Vec4|Rect} srcRect
326  * @param {Vec4|Rect} destRect
327  * @returns {object}
328  */
329 
330 struct CanvasCommand {
331  enum Variant {
332  Invalid,
333  SetStrokeWidth,
334  SetColor,
335  SetHints,
336  SetBlendMode,
337  SetFont,
338  ClearRect,
339  FillPath,
340  FillRect,
341  FillEllipse,
342  FillText,
343  StrokePath,
344  StrokeRect,
345  StrokeArc,
346  StrokeEllipse,
347  Point,
348  Line,
349  ImageCopy,
350  };
351 
352  enum RenderHint {
353  NoPrimitiveAntialiasing = (1 << 0),
354  NoTextAntialiasing = (1 << 1),
355  NearestImageScaling = (1 << 2),
356  };
357 
358  static CanvasCommand none() {
359  return CanvasCommand {};
360  }
361 
362  static CanvasCommand setStrokeWidth(qreal width) {
363  CanvasCommand cmd;
364  cmd.kind = SetStrokeWidth;
365  cmd._float[0] = width;
366  return cmd;
367  }
368 
369  static CanvasCommand setColor(const QColor& color) {
370  CanvasCommand cmd;
371  cmd.kind = SetColor;
372  cmd._color = color;
373  return cmd;
374  }
375 
376  static CanvasCommand setHints(int hints) {
377  CanvasCommand cmd;
378  cmd.kind = SetHints;
379  cmd._int[0] = hints;
380  return cmd;
381  }
382 
383  static CanvasCommand setBlendMode(int mode) {
384  CanvasCommand cmd;
385  cmd.kind = SetBlendMode;
386  cmd._int[0] = mode;
387  return cmd;
388  }
389 
390  static CanvasCommand setFont(const QString& family, int size, int weight, bool italic) {
391  CanvasCommand cmd;
392  cmd.kind = SetFont;
393  cmd._text = family;
394  cmd._int[0] = size;
395  cmd._int[1] = weight;
396  cmd._int[2] = italic;
397  return cmd;
398  }
399 
400  static CanvasCommand clearRect(int x, int y, int w, int h) {
401  CanvasCommand cmd;
402  cmd.kind = ClearRect;
403  cmd._int[0] = x;
404  cmd._int[1] = y;
405  cmd._int[2] = w;
406  cmd._int[3] = h;
407  return cmd;
408  }
409 
410  static CanvasCommand fillPath(const QPainterPath& path) {
411  CanvasCommand cmd;
412  cmd.kind = FillPath;
413  cmd._paintPath = path;
414  return cmd;
415  }
416 
417  static CanvasCommand fillRect(const QRectF& rect) {
418  CanvasCommand cmd;
419  cmd.kind = FillRect;
420  return CanvasCommand { .kind = FillRect, ._rect = rect };
421  }
422 
423  static CanvasCommand fillEllipse(const QRectF& rect) {
424  CanvasCommand cmd;
425  cmd.kind = FillEllipse;
426  cmd._rect = rect;
427  return cmd;
428  }
429 
430  static CanvasCommand fillText(const QString& text, const QRectF& rect, int flag) {
431  CanvasCommand cmd;
432  cmd.kind = FillText;
433  cmd._text = text;
434  cmd._rect = rect;
435  cmd._int[0] = flag;
436  return cmd;
437  }
438 
439  static CanvasCommand strokePath(const QPainterPath& path) {
440  CanvasCommand cmd;
441  cmd.kind = StrokePath;
442  cmd._paintPath = path;
443  return cmd;
444  }
445 
446  static CanvasCommand strokeRect(const QRectF& rect) {
447  CanvasCommand cmd;
448  cmd.kind = StrokeRect;
449  cmd._rect = rect;
450  return cmd;
451  }
452 
453  static CanvasCommand strokeArc(const QRectF& rect, qreal startAngle, qreal spanAngle) {
454  CanvasCommand cmd;
455  cmd.kind = StrokeArc;
456  cmd._rect = rect;
457  cmd._float[0] = startAngle;
458  cmd._float[1] = spanAngle;
459  return cmd;
460  }
461 
462  static CanvasCommand strokeEllipse(const QRectF& rect) {
463  CanvasCommand cmd;
464  cmd.kind = StrokeEllipse;
465  cmd._rect = rect;
466  return cmd;
467  }
468 
469  static CanvasCommand point(qreal x, qreal y) {
470  CanvasCommand cmd;
471  cmd.kind = Point;
472  cmd._point = QPointF(x, y);
473  return cmd;
474  }
475 
476  static CanvasCommand line(qreal x1, qreal y1, qreal x2, qreal y2) {
477  CanvasCommand cmd;
478  cmd.kind = Line;
479  cmd._line = QLineF(x1, y1, x2, y2);
480  return cmd;
481  }
482 
483  static CanvasCommand imageCopy(const CanvasImage& image, const QRectF& src, const QRectF& dst) {
484  CanvasCommand cmd;
485  cmd.kind = ImageCopy;
486  cmd._rect = src;
487  cmd._rect2 = dst;
488  cmd._image = image;
489  return cmd;
490  }
491 
492  Variant kind = Invalid;
493 
494  QRectF _rect = QRectF();
495  QRectF _rect2 = QRectF();
496  QString _text = QString();
497  QPointF _point = QPointF();
498  QLineF _line = QLineF();
499  qreal _float[4] = {};
500  int _int[4] = {};
501  QColor _color = {};
502  QPainterPath _paintPath = QPainterPath();
503  CanvasImage _image = {};
504 };
505 
506 void registerCanvasMetaTypes(ScriptEngine *engine);
507 
508 Q_DECLARE_METATYPE(CanvasCommand)
509 ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand& cmd);
510 bool canvasCommandFromScriptValue(const ScriptValue& object, CanvasCommand& cmd);
511 CanvasCommand canvasCommandFromScriptValue(const ScriptValue& object);
512 
513 Q_DECLARE_METATYPE(QPainterPath)
514 ScriptValue qPainterPathToScriptValue(ScriptEngine* engine, const QPainterPath& path);
515 bool qPainterPathFromScriptValue(const ScriptValue& object, QPainterPath& path);
516 QPainterPath qPainterPathFromScriptValue(const ScriptValue& object);
517 
518 Q_DECLARE_METATYPE(QVector<CanvasCommand>)
519 ScriptValue qVectorCanvasCommandToScriptValue(ScriptEngine* engine, const QVector<CanvasCommand>& list);
520 bool qVectorCanvasCommandFromScriptValue(const ScriptValue& object, QVector<CanvasCommand>& list);
521 QVector<CanvasCommand> qVectorCanvasCommandFromScriptValue(const ScriptValue& object);
522 
523 Q_DECLARE_METATYPE(CanvasImage)
524 ScriptValue canvasImageToScriptValue(ScriptEngine* engine, const CanvasImage& img);
525 bool canvasImageFromScriptValue(const ScriptValue& object, CanvasImage& img);
526 CanvasImage canvasImageFromScriptValue(const ScriptValue& object);
527 
528 Q_DECLARE_METATYPE(CanvasPathElement)
529 ScriptValue canvasPathElementToScriptValue(ScriptEngine* engine, const CanvasPathElement& elem);
530 bool canvasPathElementFromScriptValue(const ScriptValue& object, CanvasPathElement& elem);
531 CanvasPathElement canvasPathElementFromScriptValue(const ScriptValue& object);
532 
533 #endif // hifi_CanvasCommand_h
534 
Provides an engine-independent interface for a scripting engine.
Definition: ScriptEngine.h:93
[ScriptInterface] Provides an engine-independent interface for QScriptValue
Definition: ScriptValue.h:40