xbintsc 0.5.38 → 0.5.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/codegen/generator/module.js +23 -7
  3. package/dist/src/codegen/generator/module.js.map +1 -1
  4. package/dist/src/driver/version.d.ts +1 -1
  5. package/dist/src/driver/version.js +1 -1
  6. package/dist/src/extensions/gui/html.js +9 -2
  7. package/dist/src/extensions/gui/html.js.map +1 -1
  8. package/dist/tests/codegen/closures.test.js +7 -0
  9. package/dist/tests/codegen/closures.test.js.map +1 -1
  10. package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
  11. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  12. package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
  13. package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
  14. package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
  15. package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
  16. package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
  17. package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
  18. package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
  19. package/dist/tests/e2e/gui-perf.test.js +283 -0
  20. package/dist/tests/e2e/gui-perf.test.js.map +1 -0
  21. package/dist/tests/e2e/gui-script-import-order.test.js +9 -1
  22. package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -1
  23. package/dist/tests/e2e/gui-webgl.test.js +4 -4
  24. package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
  25. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
  26. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
  27. package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
  28. package/dist/tests/extensions/gui.test.js +23 -0
  29. package/dist/tests/extensions/gui.test.js.map +1 -1
  30. package/doc/gui/api.md +28 -2
  31. package/doc/gui/canvas.md +110 -27
  32. package/doc/gui/known-issues.md +32 -10
  33. package/doc/gui/rendering.md +77 -0
  34. package/doc/gui/runtime.md +18 -3
  35. package/doc/gui/webgl.md +38 -19
  36. package/doc/gui.md +33 -1
  37. package/doc/zh-CN/gui/api.md +25 -2
  38. package/doc/zh-CN/gui/canvas.md +95 -26
  39. package/doc/zh-CN/gui/known-issues.md +21 -4
  40. package/doc/zh-CN/gui/rendering.md +59 -0
  41. package/doc/zh-CN/gui/runtime.md +13 -3
  42. package/doc/zh-CN/gui/webgl.md +27 -5
  43. package/doc/zh-CN/gui.md +25 -2
  44. package/package.json +7 -7
  45. package/runtime/ext_gui/browser.cpp +3 -1
  46. package/runtime/ext_gui/canvas.cpp +135 -89
  47. package/runtime/ext_gui/canvas.h +116 -0
  48. package/runtime/ext_gui/canvas_clip.cpp +143 -0
  49. package/runtime/ext_gui/canvas_composite.cpp +241 -0
  50. package/runtime/ext_gui/canvas_coverage.cpp +241 -0
  51. package/runtime/ext_gui/canvas_gradient.cpp +41 -4
  52. package/runtime/ext_gui/canvas_image_data.cpp +4 -0
  53. package/runtime/ext_gui/canvas_internal.h +34 -9
  54. package/runtime/ext_gui/canvas_raster.cpp +207 -178
  55. package/runtime/ext_gui/canvas_shadow.cpp +161 -0
  56. package/runtime/ext_gui/canvas_stroke.cpp +37 -9
  57. package/runtime/ext_gui/canvas_style.cpp +59 -0
  58. package/runtime/ext_gui/canvas_text.cpp +140 -0
  59. package/runtime/ext_gui/css.h +7 -0
  60. package/runtime/ext_gui/css_sheet.cpp +22 -0
  61. package/runtime/ext_gui/document.cpp +25 -1
  62. package/runtime/ext_gui/document.h +8 -0
  63. package/runtime/ext_gui/dom_api_canvas.cpp +16 -1
  64. package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
  65. package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
  66. package/runtime/ext_gui/dom_api_internal.h +36 -37
  67. package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
  68. package/runtime/ext_gui/dom_api_proto.cpp +18 -0
  69. package/runtime/ext_gui/dom_api_webgl.cpp +19 -9
  70. package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
  71. package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
  72. package/runtime/ext_gui/gradient_values.cpp +12 -1
  73. package/runtime/ext_gui/gui.cpp +5 -0
  74. package/runtime/ext_gui/gui_canvas.cpp +4 -0
  75. package/runtime/ext_gui/gui_engine.h +30 -0
  76. package/runtime/ext_gui/gui_events.cpp +182 -58
  77. package/runtime/ext_gui/gui_media.cpp +3 -0
  78. package/runtime/ext_gui/image.cpp +69 -0
  79. package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
  80. package/runtime/ext_gui/layout.cpp +4 -0
  81. package/runtime/ext_gui/paint.cpp +19 -0
  82. package/runtime/ext_gui/paint_background.cpp +18 -1
  83. package/runtime/ext_gui/perf.cpp +171 -0
  84. package/runtime/ext_gui/perf.h +127 -0
  85. package/runtime/ext_gui/renderer.cpp +109 -41
  86. package/runtime/ext_gui/renderer.h +33 -9
  87. package/runtime/ext_gui/renderer_image.cpp +135 -65
  88. package/runtime/ext_gui/renderer_internal.h +15 -3
  89. package/runtime/ext_gui/renderer_text.cpp +24 -12
  90. package/runtime/ext_gui/style.cpp +101 -30
  91. package/runtime/ext_gui/style.h +6 -3
  92. package/runtime/ext_gui/style_print.cpp +7 -1
  93. package/runtime/ext_gui/text.cpp +23 -4
  94. package/runtime/ext_gui/text.h +29 -0
  95. package/runtime/ext_gui/text_cache.cpp +118 -0
  96. package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
  97. package/runtime/ext_gui/webgl.h +48 -146
  98. package/runtime/ext_gui/webgl_blit.cpp +12 -1
  99. package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
  100. package/runtime/ext_gui/webgl_buffer.cpp +59 -1
  101. package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
  102. package/runtime/ext_gui/webgl_clear.cpp +3 -0
  103. package/runtime/ext_gui/webgl_context.cpp +29 -3
  104. package/runtime/ext_gui/webgl_draw.cpp +75 -270
  105. package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
  106. package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
  107. package/runtime/ext_gui/webgl_draw_state.h +130 -0
  108. package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
  109. package/runtime/ext_gui/webgl_objects.h +148 -0
  110. package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
  111. package/runtime/ext_gui/webgl_program_internal.h +35 -0
  112. package/runtime/ext_gui/webgl_shader.cpp +22 -30
  113. package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
  114. package/runtime/ext_gui/window.cpp +56 -1
  115. package/src/codegen/generator/module.ts +22 -7
  116. package/src/driver/version.ts +1 -1
  117. package/src/extensions/gui/html.ts +9 -2
@@ -0,0 +1,241 @@
1
+ /*
2
+ * xbintsc GUI engine — Canvas2D `globalCompositeOperation`.
3
+ *
4
+ * Every raster path funnels its per-pixel work through `blend_pixel_mode`,
5
+ * which implements the Porter-Duff operators and the separable/non-separable
6
+ * blend modes from the CSS compositing specification. Colours are kept
7
+ * non-premultiplied (the canvas bitmap is RGBA8 straight alpha); the composite
8
+ * is evaluated in premultiplied form and divided back out.
9
+ */
10
+
11
+ #include "canvas_internal.h"
12
+
13
+ #include <algorithm>
14
+ #include <cmath>
15
+
16
+ namespace xtgui {
17
+ namespace canvas_detail {
18
+
19
+ namespace {
20
+
21
+ float clamp_unit(float value) { return value < 0.0f ? 0.0f : (value > 1.0f ? 1.0f : value); }
22
+
23
+ float channel_blend(int mode, float s, float d) {
24
+ switch (mode) {
25
+ case kCanvasCompositeMultiply:
26
+ return s * d;
27
+ case kCanvasCompositeScreen:
28
+ return s + d - s * d;
29
+ case kCanvasCompositeOverlay:
30
+ return d <= 0.5f ? 2.0f * s * d : 1.0f - 2.0f * (1.0f - s) * (1.0f - d);
31
+ case kCanvasCompositeDarken:
32
+ return std::min(s, d);
33
+ case kCanvasCompositeLighten:
34
+ return std::max(s, d);
35
+ case kCanvasCompositeColorDodge:
36
+ if (d <= 0.0f) return 0.0f;
37
+ if (s >= 1.0f) return 1.0f;
38
+ return std::min(1.0f, d / (1.0f - s));
39
+ case kCanvasCompositeColorBurn:
40
+ if (d >= 1.0f) return 1.0f;
41
+ if (s <= 0.0f) return 0.0f;
42
+ return 1.0f - std::min(1.0f, (1.0f - d) / s);
43
+ case kCanvasCompositeHardLight:
44
+ return s <= 0.5f ? 2.0f * s * d : 1.0f - 2.0f * (1.0f - s) * (1.0f - d);
45
+ case kCanvasCompositeSoftLight: {
46
+ if (s <= 0.5f) return d - (1.0f - 2.0f * s) * d * (1.0f - d);
47
+ float d2 = d <= 0.25f ? ((16.0f * d - 12.0f) * d + 4.0f) * d : std::sqrt(d);
48
+ return d + (2.0f * s - 1.0f) * (d2 - d);
49
+ }
50
+ case kCanvasCompositeDifference:
51
+ return std::fabs(s - d);
52
+ case kCanvasCompositeExclusion:
53
+ return s + d - 2.0f * s * d;
54
+ default:
55
+ return s;
56
+ }
57
+ }
58
+
59
+ float luminance(const float c[3]) { return 0.3f * c[0] + 0.59f * c[1] + 0.11f * c[2]; }
60
+
61
+ void clip_color(float c[3]) {
62
+ const float l = luminance(c);
63
+ float n = std::min(c[0], std::min(c[1], c[2]));
64
+ float x = std::max(c[0], std::max(c[1], c[2]));
65
+ if (n < 0.0f) {
66
+ const float scale = l / (l - n);
67
+ for (int i = 0; i < 3; i++) c[i] = l + (c[i] - l) * scale;
68
+ }
69
+ if (x > 1.0f) {
70
+ const float scale = (1.0f - l) / (x - l);
71
+ for (int i = 0; i < 3; i++) c[i] = l + (c[i] - l) * scale;
72
+ }
73
+ }
74
+
75
+ void set_luminance(float c[3], float l) {
76
+ const float delta = l - luminance(c);
77
+ for (int i = 0; i < 3; i++) c[i] += delta;
78
+ clip_color(c);
79
+ }
80
+
81
+ float saturation(const float c[3]) {
82
+ return std::max(c[0], std::max(c[1], c[2])) - std::min(c[0], std::min(c[1], c[2]));
83
+ }
84
+
85
+ void set_saturation(float c[3], float s) {
86
+ int maxIndex = 0;
87
+ int minIndex = 0;
88
+ for (int i = 1; i < 3; i++) {
89
+ if (c[i] > c[maxIndex]) maxIndex = i;
90
+ if (c[i] < c[minIndex]) minIndex = i;
91
+ }
92
+ if (maxIndex == minIndex) return;
93
+ float out[3] = {0.0f, 0.0f, 0.0f};
94
+ out[maxIndex] = s;
95
+ out[minIndex] = 0.0f;
96
+ int middle = 3 - maxIndex - minIndex;
97
+ out[middle] = s * (c[middle] - c[minIndex]) / (c[maxIndex] - c[minIndex]);
98
+ for (int i = 0; i < 3; i++) c[i] = out[i];
99
+ }
100
+
101
+ /** Non-separable modes (`hue`…`luminosity`). */
102
+ void non_separable_blend(int mode, const float s[3], const float d[3], float out[3]) {
103
+ float source[3] = {s[0], s[1], s[2]};
104
+ float dest[3] = {d[0], d[1], d[2]};
105
+ switch (mode) {
106
+ case kCanvasCompositeHue:
107
+ set_saturation(source, saturation(dest));
108
+ set_luminance(source, luminance(dest));
109
+ break;
110
+ case kCanvasCompositeSaturation:
111
+ set_saturation(dest, saturation(source));
112
+ set_luminance(dest, luminance(dest));
113
+ for (int i = 0; i < 3; i++) source[i] = dest[i];
114
+ break;
115
+ case kCanvasCompositeColor:
116
+ set_luminance(source, luminance(dest));
117
+ break;
118
+ case kCanvasCompositeLuminosity:
119
+ set_luminance(dest, luminance(source));
120
+ for (int i = 0; i < 3; i++) source[i] = dest[i];
121
+ break;
122
+ default:
123
+ break;
124
+ }
125
+ for (int i = 0; i < 3; i++) out[i] = clamp_unit(source[i]);
126
+ }
127
+
128
+ bool is_non_separable(int mode) {
129
+ return mode == kCanvasCompositeHue || mode == kCanvasCompositeSaturation ||
130
+ mode == kCanvasCompositeColor || mode == kCanvasCompositeLuminosity;
131
+ }
132
+
133
+ bool is_separable_blend(int mode) {
134
+ return mode >= kCanvasCompositeMultiply && mode <= kCanvasCompositeExclusion && !is_non_separable(mode);
135
+ }
136
+
137
+ } // namespace
138
+
139
+ void blend_pixel_mode(unsigned char *pixel, const float color[4], float coverage, float alpha,
140
+ int composite) {
141
+ if (composite == kCanvasCompositeSourceOver && alpha >= 0.999f && coverage >= 0.999f &&
142
+ color[3] >= 0.999f) {
143
+ pixel[0] = (unsigned char)(clamp_unit(color[0]) * 255.0f + 0.5f);
144
+ pixel[1] = (unsigned char)(clamp_unit(color[1]) * 255.0f + 0.5f);
145
+ pixel[2] = (unsigned char)(clamp_unit(color[2]) * 255.0f + 0.5f);
146
+ pixel[3] = 255;
147
+ return;
148
+ }
149
+
150
+ float src[3] = {clamp_unit(color[0]), clamp_unit(color[1]), clamp_unit(color[2])};
151
+ const float sa = clamp_unit(color[3] * alpha);
152
+ float dst[3] = {pixel[0] / 255.0f, pixel[1] / 255.0f, pixel[2] / 255.0f};
153
+ const float da = pixel[3] / 255.0f;
154
+
155
+ float outA = 0.0f;
156
+ float outC[3] = {0.0f, 0.0f, 0.0f}; // premultiplied
157
+
158
+ if (is_separable_blend(composite) || is_non_separable(composite)) {
159
+ float blended[3];
160
+ if (is_non_separable(composite)) {
161
+ non_separable_blend(composite, src, dst, blended);
162
+ } else {
163
+ for (int i = 0; i < 3; i++) blended[i] = channel_blend(composite, src[i], dst[i]);
164
+ }
165
+ outA = sa + da - sa * da;
166
+ for (int i = 0; i < 3; i++) {
167
+ outC[i] = (1.0f - da) * src[i] * sa + (1.0f - sa) * dst[i] * da + sa * da * blended[i];
168
+ }
169
+ } else {
170
+ switch (composite) {
171
+ case kCanvasCompositeSourceIn:
172
+ outA = sa * da;
173
+ for (int i = 0; i < 3; i++) outC[i] = src[i] * sa * da;
174
+ break;
175
+ case kCanvasCompositeSourceOut:
176
+ outA = sa * (1.0f - da);
177
+ for (int i = 0; i < 3; i++) outC[i] = src[i] * sa * (1.0f - da);
178
+ break;
179
+ case kCanvasCompositeSourceAtop:
180
+ outA = da;
181
+ for (int i = 0; i < 3; i++) outC[i] = src[i] * sa * da + dst[i] * da * (1.0f - sa);
182
+ break;
183
+ case kCanvasCompositeDestinationOver:
184
+ outA = da + sa * (1.0f - da);
185
+ for (int i = 0; i < 3; i++) outC[i] = dst[i] * da + src[i] * sa * (1.0f - da);
186
+ break;
187
+ case kCanvasCompositeDestinationIn:
188
+ outA = da * sa;
189
+ for (int i = 0; i < 3; i++) outC[i] = dst[i] * da * sa;
190
+ break;
191
+ case kCanvasCompositeDestinationOut:
192
+ outA = da * (1.0f - sa);
193
+ for (int i = 0; i < 3; i++) outC[i] = dst[i] * da * (1.0f - sa);
194
+ break;
195
+ case kCanvasCompositeDestinationAtop:
196
+ outA = sa;
197
+ for (int i = 0; i < 3; i++) outC[i] = dst[i] * da * sa + src[i] * sa * (1.0f - da);
198
+ break;
199
+ case kCanvasCompositeLighter:
200
+ outA = std::min(1.0f, sa + da);
201
+ for (int i = 0; i < 3; i++) outC[i] = std::min(1.0f, src[i] * sa + dst[i] * da);
202
+ break;
203
+ case kCanvasCompositeCopy:
204
+ outA = sa;
205
+ for (int i = 0; i < 3; i++) outC[i] = src[i] * sa;
206
+ break;
207
+ case kCanvasCompositeXor:
208
+ outA = sa * (1.0f - da) + da * (1.0f - sa);
209
+ for (int i = 0; i < 3; i++) {
210
+ outC[i] = src[i] * sa * (1.0f - da) + dst[i] * da * (1.0f - sa);
211
+ }
212
+ break;
213
+ default: // source-over
214
+ outA = sa + da * (1.0f - sa);
215
+ for (int i = 0; i < 3; i++) outC[i] = src[i] * sa + dst[i] * da * (1.0f - sa);
216
+ break;
217
+ }
218
+ }
219
+
220
+ /* Partial coverage (anti-aliasing or a clip edge) interpolates between the
221
+ * untouched destination and the full composite, which is correct for every
222
+ * operator, including `destination-in`/`copy`. */
223
+ if (coverage < 0.999f) {
224
+ const float c = clamp_unit(coverage);
225
+ outA = da * (1.0f - c) + outA * c;
226
+ for (int i = 0; i < 3; i++) outC[i] = dst[i] * da * (1.0f - c) + outC[i] * c;
227
+ }
228
+
229
+ if (outA <= 0.00001f) {
230
+ pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
231
+ return;
232
+ }
233
+ const float inverse = 1.0f / outA;
234
+ for (int i = 0; i < 3; i++) {
235
+ pixel[i] = (unsigned char)(clamp_unit(outC[i] * inverse) * 255.0f + 0.5f);
236
+ }
237
+ pixel[3] = (unsigned char)(clamp_unit(outA) * 255.0f + 0.5f);
238
+ }
239
+
240
+ } // namespace canvas_detail
241
+ } // namespace xtgui
@@ -0,0 +1,241 @@
1
+ /*
2
+ * xbintsc GUI engine — path coverage rasteriser.
3
+ *
4
+ * Fills a device-space polygon with 4 vertical sub-scanlines per pixel row and
5
+ * analytic horizontal coverage, using the non-zero winding rule or the even-odd
6
+ * rule. Two properties keep this cheap for a multi-element scene:
7
+ *
8
+ * - edges are bucketed into an **active-edge list**, so a row only pays for
9
+ * the edges that actually cross it (the naive version scanned every edge of
10
+ * every sub-scanline);
11
+ * - only the **covered span of each row** is materialised, so a thin diagonal
12
+ * stroke costs its own pixels rather than its bounding box.
13
+ *
14
+ * The coverage is returned as concatenated row spans (`CoverageGrid`); blending
15
+ * and clip-mask construction live in `canvas_raster.cpp` / `canvas_clip.cpp`.
16
+ */
17
+
18
+ #include "canvas_internal.h"
19
+
20
+ #include <algorithm>
21
+ #include <cmath>
22
+ #include <vector>
23
+
24
+ namespace xtgui {
25
+ namespace canvas_detail {
26
+
27
+ namespace {
28
+
29
+ constexpr int kSubSamples = 4;
30
+
31
+ /** One edge crossing on the current sub-scanline. */
32
+ struct Crossing {
33
+ float x;
34
+ int winding;
35
+ };
36
+
37
+ /** Add the horizontal span `[xa, xb)` into a coverage row whose first device
38
+ * pixel is `rowOriginX`. */
39
+ void add_span(float *row, int rowOriginX, float xa, float xb, float weight, int clipX0,
40
+ int clipX1) {
41
+ if (xb <= xa) return;
42
+ if (xa < (float)clipX0) xa = (float)clipX0;
43
+ if (xb > (float)clipX1) xb = (float)clipX1;
44
+ if (xb <= xa) return;
45
+ int ia = (int)std::floor(xa);
46
+ int ib = (int)std::ceil(xb) - 1;
47
+ if (ib < ia) return;
48
+ if (ia == ib) {
49
+ row[ia - rowOriginX] += (xb - xa) * weight;
50
+ return;
51
+ }
52
+ row[ia - rowOriginX] += ((float)(ia + 1) - xa) * weight;
53
+ for (int x = ia + 1; x < ib; x++) row[x - rowOriginX] += weight;
54
+ float last = xb - (float)ib;
55
+ if (last > 0.0f) row[ib - rowOriginX] += last * weight;
56
+ }
57
+
58
+ /** Order crossings left to right. A scanline usually has a handful of them, so
59
+ * an insertion sort beats `std::sort`'s setup cost; a pathological path still
60
+ * falls back to the library sort. */
61
+ void sort_crossings(std::vector<Crossing> &crossings) {
62
+ const size_t count = crossings.size();
63
+ if (count > 16) {
64
+ std::sort(crossings.begin(), crossings.end(),
65
+ [](const Crossing &a, const Crossing &b) { return a.x < b.x; });
66
+ return;
67
+ }
68
+ for (size_t i = 1; i < count; i++) {
69
+ const Crossing value = crossings[i];
70
+ size_t j = i;
71
+ while (j > 0 && crossings[j - 1].x > value.x) {
72
+ crossings[j] = crossings[j - 1];
73
+ j -= 1;
74
+ }
75
+ crossings[j] = value;
76
+ }
77
+ }
78
+
79
+ } // namespace
80
+
81
+ void compute_coverage(int width, int height,
82
+ const std::vector<std::vector<CanvasPoint>> &loops, bool even_odd,
83
+ CoverageGrid *out) {
84
+ out->x0 = out->y0 = out->x1 = out->y1 = 0;
85
+ out->values.clear();
86
+ out->row_offset.clear();
87
+ out->row_x.clear();
88
+ if (width <= 0 || height <= 0 || loops.empty()) return;
89
+ bool havePoint = false;
90
+ float minX = 0.0f, maxX = 0.0f, minY = 0.0f, maxY = 0.0f;
91
+ for (const std::vector<CanvasPoint> &loop : loops) {
92
+ for (const CanvasPoint &point : loop) {
93
+ if (!havePoint) {
94
+ minX = maxX = point.x;
95
+ minY = maxY = point.y;
96
+ havePoint = true;
97
+ } else {
98
+ minX = std::min(minX, point.x);
99
+ maxX = std::max(maxX, point.x);
100
+ minY = std::min(minY, point.y);
101
+ maxY = std::max(maxY, point.y);
102
+ }
103
+ }
104
+ }
105
+ if (!havePoint) return;
106
+ int x0 = std::max(0, (int)std::floor(minX));
107
+ int x1 = std::min(width, (int)std::ceil(maxX));
108
+ int y0 = std::max(0, (int)std::floor(minY));
109
+ int y1 = std::min(height, (int)std::ceil(maxY));
110
+ if (x1 <= x0 || y1 <= y0) return;
111
+
112
+ out->x0 = x0;
113
+ out->y0 = y0;
114
+ out->x1 = x1;
115
+ out->y1 = y1;
116
+ out->row_offset.reserve((size_t)(y1 - y0) + 1);
117
+ out->row_x.reserve((size_t)(y1 - y0));
118
+ out->row_offset.push_back(0);
119
+ const float weight = 1.0f / (float)kSubSamples;
120
+
121
+ /* Build one edge record per non-horizontal segment and the row range it can
122
+ * cross. */
123
+ struct Edge {
124
+ float x0 = 0.0f, y0 = 0.0f; // upper endpoint
125
+ float x1 = 0.0f, y1 = 0.0f; // lower endpoint
126
+ int winding = 0;
127
+ int first_row = 0;
128
+ int last_row = 0; // exclusive
129
+ };
130
+ std::vector<Edge> edges;
131
+ for (const std::vector<CanvasPoint> &loop : loops) {
132
+ if (loop.size() < 3) continue;
133
+ for (size_t i = 0; i < loop.size(); i++) {
134
+ const CanvasPoint &p0 = loop[i];
135
+ const CanvasPoint &p1 = loop[(i + 1) % loop.size()];
136
+ if (p0.y == p1.y) continue;
137
+ Edge edge;
138
+ if (p0.y < p1.y) {
139
+ edge.x0 = p0.x;
140
+ edge.y0 = p0.y;
141
+ edge.x1 = p1.x;
142
+ edge.y1 = p1.y;
143
+ edge.winding = 1;
144
+ } else {
145
+ edge.x0 = p1.x;
146
+ edge.y0 = p1.y;
147
+ edge.x1 = p0.x;
148
+ edge.y1 = p0.y;
149
+ edge.winding = -1;
150
+ }
151
+ edge.first_row = (int)std::floor(edge.y0);
152
+ edge.last_row = (int)std::ceil(edge.y1);
153
+ edges.push_back(edge);
154
+ }
155
+ }
156
+ if (edges.empty()) return;
157
+ std::sort(edges.begin(), edges.end(),
158
+ [](const Edge &a, const Edge &b) { return a.first_row < b.first_row; });
159
+
160
+ struct Span {
161
+ float xa = 0.0f;
162
+ float xb = 0.0f;
163
+ };
164
+ std::vector<Crossing> crossings;
165
+ std::vector<Span> spans;
166
+ std::vector<float> row_values;
167
+ std::vector<int> active;
168
+ size_t nextEdge = 0;
169
+ for (int py = y0; py < y1; py++) {
170
+ while (nextEdge < edges.size() && edges[nextEdge].first_row <= py) {
171
+ active.push_back((int)nextEdge);
172
+ nextEdge += 1;
173
+ }
174
+ /* Drop edges whose bottom is above this row. */
175
+ size_t kept = 0;
176
+ for (size_t i = 0; i < active.size(); i++) {
177
+ const Edge &edge = edges[(size_t)active[i]];
178
+ if (edge.last_row > py) active[kept++] = active[i];
179
+ }
180
+ active.resize(kept);
181
+ spans.clear();
182
+ for (int sample = 0; sample < kSubSamples; sample++) {
183
+ float sy = (float)py + ((float)sample + 0.5f) / (float)kSubSamples;
184
+ crossings.clear();
185
+ for (int index : active) {
186
+ const Edge &edge = edges[(size_t)index];
187
+ if (sy < edge.y0 || sy >= edge.y1) continue;
188
+ const float t = (sy - edge.y0) / (edge.y1 - edge.y0);
189
+ Crossing crossing;
190
+ crossing.x = edge.x0 + t * (edge.x1 - edge.x0);
191
+ crossing.winding = edge.winding;
192
+ crossings.push_back(crossing);
193
+ }
194
+ if (crossings.empty()) continue;
195
+ sort_crossings(crossings);
196
+ if (even_odd) {
197
+ /* Every second crossing toggles the covered span, regardless of the
198
+ * direction each contour is wound. */
199
+ for (size_t i = 0; i + 1 < crossings.size(); i += 2) {
200
+ spans.push_back(Span{crossings[i].x, crossings[i + 1].x});
201
+ }
202
+ continue;
203
+ }
204
+ int winding = 0;
205
+ float spanStart = 0.0f;
206
+ for (const Crossing &crossing : crossings) {
207
+ if (winding == 0) spanStart = crossing.x;
208
+ winding += crossing.winding;
209
+ if (winding == 0) spans.push_back(Span{spanStart, crossing.x});
210
+ }
211
+ }
212
+ if (spans.empty()) {
213
+ out->row_offset.push_back((uint32_t)out->values.size());
214
+ out->row_x.push_back(0);
215
+ continue;
216
+ }
217
+ float minSpan = spans[0].xa;
218
+ float maxSpan = spans[0].xb;
219
+ for (const Span &span : spans) {
220
+ minSpan = std::min(minSpan, span.xa);
221
+ maxSpan = std::max(maxSpan, span.xb);
222
+ }
223
+ const int rx0 = std::max(x0, (int)std::floor(minSpan));
224
+ const int rx1 = std::min(x1, (int)std::ceil(maxSpan));
225
+ if (rx1 <= rx0) {
226
+ out->row_offset.push_back((uint32_t)out->values.size());
227
+ out->row_x.push_back(0);
228
+ continue;
229
+ }
230
+ row_values.assign((size_t)(rx1 - rx0), 0.0f);
231
+ for (const Span &span : spans) {
232
+ add_span(row_values.data(), rx0, span.xa, span.xb, weight, x0, x1);
233
+ }
234
+ out->row_x.push_back(rx0);
235
+ out->values.insert(out->values.end(), row_values.begin(), row_values.end());
236
+ out->row_offset.push_back((uint32_t)out->values.size());
237
+ }
238
+ }
239
+
240
+ } // namespace canvas_detail
241
+ } // namespace xtgui
@@ -55,11 +55,48 @@ bool CanvasGradient::color_at(float x, float y, float out[4]) const {
55
55
  t = ((x - x0_) * dx + (y - y0_) * dy) / lengthSquared;
56
56
  }
57
57
  } else {
58
- const float dx = x - x1_;
59
- const float dy = y - y1_;
60
- const float distance = std::sqrt(dx * dx + dy * dy);
58
+ /* Two-circle radial gradient (CSS/Canvas): find the smallest t >= 0 for
59
+ * which the point lies on the circle
60
+ * centre = c0 + t * (c1 - c0), radius = r0 + t * (r1 - r0).
61
+ * This reduces to a quadratic, and it makes a non-concentric inner circle
62
+ * produce the cone a browser draws. */
63
+ const float dx = x1_ - x0_;
64
+ const float dy = y1_ - y0_;
65
+ const float qx = x - x0_;
66
+ const float qy = y - y0_;
61
67
  const float radiusDelta = r1_ - r0_;
62
- if (std::fabs(radiusDelta) > 1e-6f) t = (distance - r0_) / radiusDelta;
68
+ const float innerDistance = std::sqrt(qx * qx + qy * qy);
69
+ const float a = dx * dx + dy * dy - radiusDelta * radiusDelta;
70
+ const float b = -2.0f * (qx * dx + qy * dy + r0_ * radiusDelta);
71
+ const float c = qx * qx + qy * qy - r0_ * r0_;
72
+ if (innerDistance <= std::fabs(r0_) + 1e-4f) {
73
+ /* Inside the inner circle every ray starts at t = 0. */
74
+ t = 0.0f;
75
+ } else if (std::fabs(a) < 1e-6f) {
76
+ if (std::fabs(b) > 1e-6f) {
77
+ t = -c / b;
78
+ } else {
79
+ t = innerDistance < std::fabs(r0_) ? 0.0f : 1.0f;
80
+ }
81
+ } else {
82
+ const float discriminant = b * b - 4.0f * a * c;
83
+ if (discriminant < 0.0f) {
84
+ t = innerDistance < std::fabs(r0_) ? 0.0f : 1.0f;
85
+ } else {
86
+ const float root = std::sqrt(discriminant);
87
+ const float t0 = (-b - root) / (2.0f * a);
88
+ const float t1 = (-b + root) / (2.0f * a);
89
+ const float low = std::min(t0, t1);
90
+ const float high = std::max(t0, t1);
91
+ if (low >= 0.0f) {
92
+ t = low;
93
+ } else if (high >= 0.0f) {
94
+ t = high;
95
+ } else {
96
+ t = innerDistance < std::fabs(r0_) ? 0.0f : 1.0f;
97
+ }
98
+ }
99
+ }
63
100
  }
64
101
  t = clamp_unit(t);
65
102
  if (t <= stops_.front().offset) {
@@ -15,6 +15,8 @@
15
15
  #include <cstring>
16
16
  #include <vector>
17
17
 
18
+ #include "perf.h"
19
+
18
20
  namespace xtgui {
19
21
 
20
22
  void Canvas2D::set_image_data(const std::vector<unsigned char> &rgba, int data_width,
@@ -38,6 +40,7 @@ void Canvas2D::set_image_data(const std::vector<unsigned char> &rgba, int data_w
38
40
  int dest_y0 = std::max(0, dy + source_y0);
39
41
  int dest_x1 = std::min(image_.width, dx + source_x1);
40
42
  int dest_y1 = std::min(image_.height, dy + source_y1);
43
+ mark_dirty((float)dest_x0, (float)dest_y0, (float)dest_x1, (float)dest_y1);
41
44
  for (int py = dest_y0; py < dest_y1; py++) {
42
45
  int source_row = py - dy;
43
46
  const unsigned char *src =
@@ -46,6 +49,7 @@ void Canvas2D::set_image_data(const std::vector<unsigned char> &rgba, int data_w
46
49
  image_.pixels.data() + ((size_t)py * (size_t)image_.width + (size_t)dest_x0) * 4;
47
50
  memcpy(dst, src, (size_t)(dest_x1 - dest_x0) * 4);
48
51
  }
52
+ g_perf_frame.canvasDraws += 1;
49
53
  generation_++;
50
54
  }
51
55
 
@@ -21,33 +21,58 @@ namespace canvas_detail {
21
21
  * pixel. `alpha` folds in `globalAlpha`. */
22
22
  void blend_pixel(unsigned char *pixel, const float color[4], float coverage, float alpha);
23
23
 
24
+ /** The full `globalCompositeOperation` matrix; `composite` is a
25
+ * `CanvasComposite` value. */
26
+ void blend_pixel_mode(unsigned char *pixel, const float color[4], float coverage, float alpha,
27
+ int composite);
28
+
29
+ /** One path's anti-aliased coverage, clipped to the canvas bitmap. Only the
30
+ * covered span of each row is stored, so a thin diagonal stroke costs its own
31
+ * pixels instead of its (potentially huge) bounding box. */
32
+ struct CoverageGrid {
33
+ int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
34
+ /** Concatenated row spans; row `r` owns `[row_offset[r], row_offset[r+1])`. */
35
+ std::vector<float> values;
36
+ /** One past-the-end index per row, plus a final entry (size = rows + 1). */
37
+ std::vector<uint32_t> row_offset;
38
+ /** Device-space x of the first stored coverage value of each row. */
39
+ std::vector<int> row_x;
40
+ bool empty() const { return x1 <= x0 || y1 <= y0; }
41
+ };
42
+
43
+ /** Rasterise `loops` (device space) into a coverage grid. */
44
+ void compute_coverage(int width, int height,
45
+ const std::vector<std::vector<CanvasPoint>> &loops, bool even_odd,
46
+ CoverageGrid *out);
47
+
24
48
  /** Fill one closed polygon loop (already in device space) with `paint`,
25
49
  * anti-aliased, blending source-over. */
26
50
  void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
27
51
  const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha,
28
- bool even_odd = false);
52
+ bool even_odd = false, const CanvasClipMask *clip = nullptr);
29
53
 
30
54
  /** Fill several loops as one path with the nonzero winding rule (so a contour
31
55
  * inside another punches a hole), or the even-odd rule. */
32
56
  void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
33
57
  const std::vector<std::vector<CanvasPoint>> &loops, const CanvasPaint &paint,
34
- float alpha, bool even_odd = false);
58
+ float alpha, bool even_odd = false, const CanvasClipMask *clip = nullptr);
35
59
 
36
60
  /** Fill a device-space axis-aligned rectangle, anti-aliased. Used by the
37
61
  * `fillRect`/`clearRect` fast path when the transform preserves axis
38
62
  * alignment. */
39
63
  void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
40
- float x1, float y1, const CanvasPaint &paint, float alpha);
64
+ float x1, float y1, const CanvasPaint &paint, float alpha,
65
+ const CanvasClipMask *clip = nullptr);
41
66
 
42
67
  /** Clear a device-space axis-aligned rectangle to transparent. */
43
68
  void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
44
- float x1, float y1);
69
+ float x1, float y1, const CanvasClipMask *clip = nullptr);
45
70
 
46
- /** Blit an RGBA8 source image into the destination rectangle using nearest
47
- * sampling, source-over. Runs in device space (no transform). */
71
+ /** Blit an RGBA8 source image into the destination rectangle, source-over.
72
+ * Runs in device space (no transform). `smooth` selects bilinear sampling. */
48
73
  void draw_image(std::vector<unsigned char> &pixels, int width, int height, const Image &source,
49
74
  float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh,
50
- float alpha);
75
+ float alpha, bool smooth, const CanvasClipMask *clip = nullptr);
51
76
 
52
77
  /** Blit an RGBA8 source through a full affine transform. The destination
53
78
  * rectangle is mapped to a parallelogram by `transform`; each device pixel is
@@ -57,14 +82,14 @@ void draw_image(std::vector<unsigned char> &pixels, int width, int height, const
57
82
  void draw_image_affine(std::vector<unsigned char> &pixels, int width, int height,
58
83
  const Image &source, float dx, float dy, float dw, float dh, float sx,
59
84
  float sy, float sw, float sh, const CanvasTransform &transform,
60
- float alpha);
85
+ float alpha, bool smooth, const CanvasClipMask *clip = nullptr);
61
86
 
62
87
  /** Composite one grayscale glyph bitmap at `(x, baselineY)` with `paint`.
63
88
  * `scale_x` < 1 condenses the bitmap horizontally (`fillText`'s `maxWidth`). */
64
89
  void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
65
90
  const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
66
91
  int top, float x, float baselineY, float scale_x, const CanvasPaint &paint,
67
- float alpha);
92
+ float alpha, const CanvasClipMask *clip = nullptr);
68
93
 
69
94
  } // namespace canvas_detail
70
95
  } // namespace xtgui