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.
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/codegen/generator/module.js +23 -7
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/html.js +9 -2
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/tests/codegen/closures.test.js +7 -0
- package/dist/tests/codegen/closures.test.js.map +1 -1
- package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
- package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
- package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
- package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
- package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
- package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
- package/dist/tests/e2e/gui-perf.test.js +283 -0
- package/dist/tests/e2e/gui-perf.test.js.map +1 -0
- package/dist/tests/e2e/gui-script-import-order.test.js +9 -1
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl.test.js +4 -4
- package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +23 -0
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/doc/gui/api.md +28 -2
- package/doc/gui/canvas.md +110 -27
- package/doc/gui/known-issues.md +32 -10
- package/doc/gui/rendering.md +77 -0
- package/doc/gui/runtime.md +18 -3
- package/doc/gui/webgl.md +38 -19
- package/doc/gui.md +33 -1
- package/doc/zh-CN/gui/api.md +25 -2
- package/doc/zh-CN/gui/canvas.md +95 -26
- package/doc/zh-CN/gui/known-issues.md +21 -4
- package/doc/zh-CN/gui/rendering.md +59 -0
- package/doc/zh-CN/gui/runtime.md +13 -3
- package/doc/zh-CN/gui/webgl.md +27 -5
- package/doc/zh-CN/gui.md +25 -2
- package/package.json +7 -7
- package/runtime/ext_gui/browser.cpp +3 -1
- package/runtime/ext_gui/canvas.cpp +135 -89
- package/runtime/ext_gui/canvas.h +116 -0
- package/runtime/ext_gui/canvas_clip.cpp +143 -0
- package/runtime/ext_gui/canvas_composite.cpp +241 -0
- package/runtime/ext_gui/canvas_coverage.cpp +241 -0
- package/runtime/ext_gui/canvas_gradient.cpp +41 -4
- package/runtime/ext_gui/canvas_image_data.cpp +4 -0
- package/runtime/ext_gui/canvas_internal.h +34 -9
- package/runtime/ext_gui/canvas_raster.cpp +207 -178
- package/runtime/ext_gui/canvas_shadow.cpp +161 -0
- package/runtime/ext_gui/canvas_stroke.cpp +37 -9
- package/runtime/ext_gui/canvas_style.cpp +59 -0
- package/runtime/ext_gui/canvas_text.cpp +140 -0
- package/runtime/ext_gui/css.h +7 -0
- package/runtime/ext_gui/css_sheet.cpp +22 -0
- package/runtime/ext_gui/document.cpp +25 -1
- package/runtime/ext_gui/document.h +8 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +16 -1
- package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
- package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
- package/runtime/ext_gui/dom_api_internal.h +36 -37
- package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
- package/runtime/ext_gui/dom_api_proto.cpp +18 -0
- package/runtime/ext_gui/dom_api_webgl.cpp +19 -9
- package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
- package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
- package/runtime/ext_gui/gradient_values.cpp +12 -1
- package/runtime/ext_gui/gui.cpp +5 -0
- package/runtime/ext_gui/gui_canvas.cpp +4 -0
- package/runtime/ext_gui/gui_engine.h +30 -0
- package/runtime/ext_gui/gui_events.cpp +182 -58
- package/runtime/ext_gui/gui_media.cpp +3 -0
- package/runtime/ext_gui/image.cpp +69 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
- package/runtime/ext_gui/layout.cpp +4 -0
- package/runtime/ext_gui/paint.cpp +19 -0
- package/runtime/ext_gui/paint_background.cpp +18 -1
- package/runtime/ext_gui/perf.cpp +171 -0
- package/runtime/ext_gui/perf.h +127 -0
- package/runtime/ext_gui/renderer.cpp +109 -41
- package/runtime/ext_gui/renderer.h +33 -9
- package/runtime/ext_gui/renderer_image.cpp +135 -65
- package/runtime/ext_gui/renderer_internal.h +15 -3
- package/runtime/ext_gui/renderer_text.cpp +24 -12
- package/runtime/ext_gui/style.cpp +101 -30
- package/runtime/ext_gui/style.h +6 -3
- package/runtime/ext_gui/style_print.cpp +7 -1
- package/runtime/ext_gui/text.cpp +23 -4
- package/runtime/ext_gui/text.h +29 -0
- package/runtime/ext_gui/text_cache.cpp +118 -0
- package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
- package/runtime/ext_gui/webgl.h +48 -146
- package/runtime/ext_gui/webgl_blit.cpp +12 -1
- package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
- package/runtime/ext_gui/webgl_buffer.cpp +59 -1
- package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
- package/runtime/ext_gui/webgl_clear.cpp +3 -0
- package/runtime/ext_gui/webgl_context.cpp +29 -3
- package/runtime/ext_gui/webgl_draw.cpp +75 -270
- package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
- package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
- package/runtime/ext_gui/webgl_draw_state.h +130 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
- package/runtime/ext_gui/webgl_objects.h +148 -0
- package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
- package/runtime/ext_gui/webgl_program_internal.h +35 -0
- package/runtime/ext_gui/webgl_shader.cpp +22 -30
- package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
- package/runtime/ext_gui/window.cpp +56 -1
- package/src/codegen/generator/module.ts +22 -7
- package/src/driver/version.ts +1 -1
- 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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
|
47
|
-
*
|
|
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
|