xbintsc 0.5.37 → 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/codegen/generator/state.js +1 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.js +1 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/deferred.d.ts +63 -0
- package/dist/src/driver/bundler/deferred.js +279 -0
- package/dist/src/driver/bundler/deferred.js.map +1 -0
- package/dist/src/driver/bundler/merge.js +214 -2
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/runtime-objects.d.ts +1 -1
- package/dist/src/driver/runtime-objects.js +1 -0
- package/dist/src/driver/runtime-objects.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 +98 -15
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/gui/index.js +1 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/gui/three.d.ts +4 -3
- package/dist/src/extensions/gui/three.js +4 -3
- package/dist/src/extensions/gui/three.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.d.ts +10 -0
- package/dist/tests/e2e/gui-script-import-order.test.js +169 -0
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
- 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/e2e/gui-webgl2-shadow.test.d.ts +10 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
- package/dist/tests/e2e/url.test.d.ts +6 -0
- package/dist/tests/e2e/url.test.js +114 -0
- package/dist/tests/e2e/url.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +45 -10
- 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 +58 -29
- package/doc/gui-scripts.md +19 -19
- package/doc/gui.md +43 -9
- package/doc/implemented/fetch.md +5 -0
- package/doc/implemented/modules.md +13 -0
- package/doc/implemented/stdlib.md +17 -0
- package/doc/unimplemented/deviations.md +1 -0
- 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 +30 -8
- package/doc/zh-CN/gui-scripts.md +14 -13
- package/doc/zh-CN/gui.md +32 -8
- package/doc/zh-CN/implemented/fetch.md +4 -0
- package/doc/zh-CN/implemented/modules.md +10 -0
- package/doc/zh-CN/implemented/stdlib.md +15 -0
- package/doc/zh-CN/unimplemented/deviations.md +1 -0
- 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 +29 -9
- package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
- 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 +32 -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/script.cpp +31 -0
- 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.h +3 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
- package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
- package/runtime/ext_gui/webgl_msl.cpp +4 -0
- 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 +34 -36
- package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
- package/runtime/ext_gui/window.cpp +56 -1
- package/runtime/rt.h +3 -0
- package/runtime/xt_fetch/async.inc +60 -0
- package/runtime/xt_url_object.c +463 -0
- package/src/codegen/generator/module.ts +22 -7
- package/src/codegen/generator/state.ts +1 -0
- package/src/codegen/generator/tables.ts +1 -0
- package/src/driver/bundler/deferred.ts +351 -0
- package/src/driver/bundler/merge.ts +219 -2
- package/src/driver/runtime-objects.ts +1 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/html.ts +100 -17
- package/src/extensions/gui/index.ts +1 -0
- package/src/extensions/gui/three.ts +4 -3
|
@@ -19,8 +19,6 @@ namespace canvas_detail {
|
|
|
19
19
|
|
|
20
20
|
namespace {
|
|
21
21
|
|
|
22
|
-
constexpr int kSubSamples = 4;
|
|
23
|
-
|
|
24
22
|
int clamp_byte(float value) {
|
|
25
23
|
int result = (int)(value + 0.5f);
|
|
26
24
|
if (result < 0) result = 0;
|
|
@@ -28,12 +26,6 @@ int clamp_byte(float value) {
|
|
|
28
26
|
return result;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
/** One edge crossing on the current sub-scanline. */
|
|
32
|
-
struct Crossing {
|
|
33
|
-
float x;
|
|
34
|
-
int winding;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
29
|
/** Resolve `paint` at a device-space pixel centre. Gradients are defined in
|
|
38
30
|
* user space, so the pixel is mapped back through the inverse transform. */
|
|
39
31
|
void paint_color_at(const CanvasPaint &paint, float x, float y, float out[4]) {
|
|
@@ -54,175 +46,157 @@ void paint_color_at(const CanvasPaint &paint, float x, float y, float out[4]) {
|
|
|
54
46
|
out[0] = out[1] = out[2] = out[3] = 0.0f;
|
|
55
47
|
}
|
|
56
48
|
|
|
49
|
+
/** True when the paint is a single colour, so the per-pixel colour resolution
|
|
50
|
+
* (a branch on gradient/pattern and a 4-float copy) can be hoisted out of the
|
|
51
|
+
* pixel loop. A solid paint is the common case in a multi-element scene. */
|
|
52
|
+
bool paint_is_solid(const CanvasPaint &paint) {
|
|
53
|
+
return paint.gradient == nullptr && paint.pattern == nullptr && paint.color != nullptr;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
void copy_color(const float color[4], float out[4]) {
|
|
57
|
+
out[0] = color[0];
|
|
58
|
+
out[1] = color[1];
|
|
59
|
+
out[2] = color[2];
|
|
60
|
+
out[3] = color[3];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
unsigned char color_byte(float value) {
|
|
64
|
+
value = value < 0.0f ? 0.0f : (value > 1.0f ? 1.0f : value);
|
|
65
|
+
return (unsigned char)(value * 255.0f + 0.5f);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** True when a source-over paint writes its colour unchanged at full coverage
|
|
69
|
+
* (opaque colour, opaque globalAlpha): the rasteriser can store bytes directly
|
|
70
|
+
* instead of running the compositing math per pixel. */
|
|
71
|
+
bool paint_is_opaque_source(const CanvasPaint &paint, float alpha) {
|
|
72
|
+
return paint_is_solid(paint) && paint.composite == kCanvasCompositeSourceOver &&
|
|
73
|
+
alpha >= 0.999f && paint.color[3] >= 0.999f;
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
} // namespace
|
|
58
77
|
|
|
59
78
|
void blend_pixel(unsigned char *pixel, const float color[4], float coverage, float alpha) {
|
|
60
|
-
|
|
61
|
-
textures, terrain and the opaque parts of every sprite. */
|
|
62
|
-
if (alpha >= 0.999f && coverage >= 0.999f && color[3] >= 0.999f) {
|
|
63
|
-
pixel[0] = (unsigned char)clamp_byte(color[0] * 255.0f);
|
|
64
|
-
pixel[1] = (unsigned char)clamp_byte(color[1] * 255.0f);
|
|
65
|
-
pixel[2] = (unsigned char)clamp_byte(color[2] * 255.0f);
|
|
66
|
-
pixel[3] = 255;
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
float sa = color[3] * coverage * alpha;
|
|
70
|
-
if (sa <= 0.0f) return;
|
|
71
|
-
if (sa > 1.0f) sa = 1.0f;
|
|
72
|
-
float da = pixel[3] / 255.0f;
|
|
73
|
-
float out_a = sa + da * (1.0f - sa);
|
|
74
|
-
if (out_a <= 0.0f) {
|
|
75
|
-
pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const float inv = 1.0f / out_a;
|
|
79
|
-
pixel[0] = (unsigned char)clamp_byte((color[0] * sa + (pixel[0] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
|
|
80
|
-
pixel[1] = (unsigned char)clamp_byte((color[1] * sa + (pixel[1] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
|
|
81
|
-
pixel[2] = (unsigned char)clamp_byte((color[2] * sa + (pixel[2] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
|
|
82
|
-
pixel[3] = (unsigned char)clamp_byte(out_a * 255.0f);
|
|
79
|
+
blend_pixel_mode(pixel, color, coverage, alpha, kCanvasCompositeSourceOver);
|
|
83
80
|
}
|
|
84
81
|
|
|
85
|
-
/**
|
|
86
|
-
static void
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
int ia = (int)std::floor(xa);
|
|
93
|
-
int ib = (int)std::ceil(xb) - 1;
|
|
94
|
-
if (ib < ia) return;
|
|
95
|
-
if (ia == ib) {
|
|
96
|
-
row[ia - rowOriginX] += (xb - xa) * weight;
|
|
97
|
-
return;
|
|
82
|
+
/** Source-over with a clip mask applied to the coverage. */
|
|
83
|
+
static void blend_clipped(unsigned char *pixel, const float color[4], float coverage, float alpha,
|
|
84
|
+
const CanvasPaint &paint, const CanvasClipMask *clip, int x, int y) {
|
|
85
|
+
if (clip != nullptr) {
|
|
86
|
+
const float clipCoverage = clip->at(x, y) / 255.0f;
|
|
87
|
+
if (clipCoverage <= 0.0f) return;
|
|
88
|
+
coverage *= clipCoverage;
|
|
98
89
|
}
|
|
99
|
-
|
|
100
|
-
for (int x = ia + 1; x < ib; x++) row[x - rowOriginX] += weight;
|
|
101
|
-
float last = xb - (float)ib;
|
|
102
|
-
if (last > 0.0f) row[ib - rowOriginX] += last * weight;
|
|
90
|
+
blend_pixel_mode(pixel, color, coverage, alpha, paint.composite);
|
|
103
91
|
}
|
|
104
92
|
|
|
105
93
|
void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
|
|
106
94
|
const std::vector<std::vector<CanvasPoint>> &loops, const CanvasPaint &paint,
|
|
107
|
-
float alpha, bool even_odd) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
int x0 = std::max(0, (int)std::floor(minX));
|
|
127
|
-
int x1 = std::min(width, (int)std::ceil(maxX));
|
|
128
|
-
int y0 = std::max(0, (int)std::floor(minY));
|
|
129
|
-
int y1 = std::min(height, (int)std::ceil(maxY));
|
|
130
|
-
if (x1 <= x0 || y1 <= y0) return;
|
|
131
|
-
|
|
132
|
-
int boxWidth = x1 - x0;
|
|
133
|
-
std::vector<float> coverage((size_t)boxWidth * (size_t)(y1 - y0), 0.0f);
|
|
134
|
-
const float weight = 1.0f / (float)kSubSamples;
|
|
135
|
-
|
|
136
|
-
std::vector<Crossing> crossings;
|
|
137
|
-
for (int py = y0; py < y1; py++) {
|
|
138
|
-
float *row = coverage.data() + (size_t)(py - y0) * (size_t)boxWidth;
|
|
139
|
-
for (int sample = 0; sample < kSubSamples; sample++) {
|
|
140
|
-
float sy = (float)py + ((float)sample + 0.5f) / (float)kSubSamples;
|
|
141
|
-
crossings.clear();
|
|
142
|
-
for (const std::vector<CanvasPoint> &loop : loops) {
|
|
143
|
-
if (loop.size() < 3) continue;
|
|
144
|
-
for (size_t i = 0; i < loop.size(); i++) {
|
|
145
|
-
const CanvasPoint &p0 = loop[i];
|
|
146
|
-
const CanvasPoint &p1 = loop[(i + 1) % loop.size()];
|
|
147
|
-
if (p0.y == p1.y) continue;
|
|
148
|
-
float top = std::min(p0.y, p1.y);
|
|
149
|
-
float bottom = std::max(p0.y, p1.y);
|
|
150
|
-
if (sy < top || sy >= bottom) continue;
|
|
151
|
-
float t = (sy - p0.y) / (p1.y - p0.y);
|
|
152
|
-
Crossing crossing;
|
|
153
|
-
crossing.x = p0.x + t * (p1.x - p0.x);
|
|
154
|
-
crossing.winding = p1.y > p0.y ? 1 : -1;
|
|
155
|
-
crossings.push_back(crossing);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (crossings.empty()) continue;
|
|
159
|
-
std::sort(crossings.begin(), crossings.end(),
|
|
160
|
-
[](const Crossing &a, const Crossing &b) { return a.x < b.x; });
|
|
161
|
-
if (even_odd) {
|
|
162
|
-
/* Every second crossing toggles the covered span, regardless of the
|
|
163
|
-
* direction each contour is wound. */
|
|
164
|
-
for (size_t i = 0; i + 1 < crossings.size(); i += 2) {
|
|
165
|
-
add_span(row, x0, crossings[i].x, crossings[i + 1].x, weight, x0, x1);
|
|
166
|
-
}
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
int winding = 0;
|
|
170
|
-
float spanStart = 0.0f;
|
|
171
|
-
for (const Crossing &crossing : crossings) {
|
|
172
|
-
if (winding == 0) spanStart = crossing.x;
|
|
173
|
-
winding += crossing.winding;
|
|
174
|
-
if (winding == 0) add_span(row, x0, spanStart, crossing.x, weight, x0, x1);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
for (int py = y0; py < y1; py++) {
|
|
180
|
-
const float *row = coverage.data() + (size_t)(py - y0) * (size_t)boxWidth;
|
|
181
|
-
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)x0) * 4;
|
|
182
|
-
for (int px = 0; px < boxWidth; px++) {
|
|
183
|
-
float c = row[px];
|
|
95
|
+
float alpha, bool even_odd, const CanvasClipMask *clip) {
|
|
96
|
+
CoverageGrid coverage;
|
|
97
|
+
compute_coverage(width, height, loops, even_odd, &coverage);
|
|
98
|
+
if (coverage.empty()) return;
|
|
99
|
+
const bool solid = paint_is_solid(paint);
|
|
100
|
+
float solid_color[4];
|
|
101
|
+
if (solid) copy_color(paint.color, solid_color);
|
|
102
|
+
const int rows = coverage.y1 - coverage.y0;
|
|
103
|
+
for (int r = 0; r < rows; r++) {
|
|
104
|
+
const uint32_t begin = coverage.row_offset[(size_t)r];
|
|
105
|
+
const uint32_t end = coverage.row_offset[(size_t)r + 1];
|
|
106
|
+
if (begin >= end) continue;
|
|
107
|
+
const int py = coverage.y0 + r;
|
|
108
|
+
const int rx = coverage.row_x[(size_t)r];
|
|
109
|
+
const float *row = coverage.values.data() + begin;
|
|
110
|
+
const int row_width = (int)(end - begin);
|
|
111
|
+
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)rx) * 4;
|
|
112
|
+
for (int i = 0; i < row_width; i++) {
|
|
113
|
+
float c = row[i];
|
|
184
114
|
if (c <= 0.0f) continue;
|
|
185
115
|
if (c > 1.0f) c = 1.0f;
|
|
186
116
|
float color[4];
|
|
187
|
-
|
|
188
|
-
|
|
117
|
+
if (solid) {
|
|
118
|
+
copy_color(solid_color, color);
|
|
119
|
+
} else {
|
|
120
|
+
paint_color_at(paint, (float)(rx + i) + 0.5f, (float)py + 0.5f, color);
|
|
121
|
+
}
|
|
122
|
+
blend_clipped(dst + (size_t)i * 4, color, c, alpha, paint, clip, rx + i, py);
|
|
189
123
|
}
|
|
190
124
|
}
|
|
191
125
|
}
|
|
192
126
|
|
|
193
127
|
void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
|
|
194
128
|
const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha,
|
|
195
|
-
bool even_odd) {
|
|
129
|
+
bool even_odd, const CanvasClipMask *clip) {
|
|
196
130
|
fill_loops(pixels, width, height, std::vector<std::vector<CanvasPoint>>{loop}, paint, alpha,
|
|
197
|
-
even_odd);
|
|
131
|
+
even_odd, clip);
|
|
198
132
|
}
|
|
199
133
|
|
|
200
134
|
void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
201
|
-
float x1, float y1, const CanvasPaint &paint, float alpha
|
|
135
|
+
float x1, float y1, const CanvasPaint &paint, float alpha,
|
|
136
|
+
const CanvasClipMask *clip) {
|
|
202
137
|
if (x1 < x0) std::swap(x0, x1);
|
|
203
138
|
if (y1 < y0) std::swap(y0, y1);
|
|
204
139
|
int ix0 = std::max(0, (int)std::floor(x0));
|
|
205
140
|
int ix1 = std::min(width, (int)std::ceil(x1));
|
|
206
141
|
int iy0 = std::max(0, (int)std::floor(y0));
|
|
207
142
|
int iy1 = std::min(height, (int)std::ceil(y1));
|
|
143
|
+
const bool solid = paint_is_solid(paint);
|
|
144
|
+
float solid_color[4];
|
|
145
|
+
if (solid) copy_color(paint.color, solid_color);
|
|
146
|
+
/* A fully opaque solid fill at full coverage is a byte store: the same bytes
|
|
147
|
+
* `blend_pixel_mode` would compute, without the per-pixel compositing math.
|
|
148
|
+
* Fractional edges (and clipped or translucent fills) keep the general path,
|
|
149
|
+
* so the output is unchanged for every other case. */
|
|
150
|
+
unsigned char opaque_bytes[4] = {0, 0, 0, 0};
|
|
151
|
+
const bool opaque = paint_is_opaque_source(paint, alpha) && clip == nullptr;
|
|
152
|
+
if (opaque) {
|
|
153
|
+
opaque_bytes[0] = color_byte(solid_color[0]);
|
|
154
|
+
opaque_bytes[1] = color_byte(solid_color[1]);
|
|
155
|
+
opaque_bytes[2] = color_byte(solid_color[2]);
|
|
156
|
+
opaque_bytes[3] = 255;
|
|
157
|
+
}
|
|
208
158
|
for (int py = iy0; py < iy1; py++) {
|
|
209
159
|
float cy = std::min(y1, (float)(py + 1)) - std::max(y0, (float)py);
|
|
210
160
|
if (cy <= 0.0f) continue;
|
|
211
161
|
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)ix0) * 4;
|
|
162
|
+
if (opaque && cy >= 0.999f) {
|
|
163
|
+
for (int px = ix0; px < ix1; px++) {
|
|
164
|
+
float cx = std::min(x1, (float)(px + 1)) - std::max(x0, (float)px);
|
|
165
|
+
if (cx <= 0.0f) continue;
|
|
166
|
+
unsigned char *pixel = dst + (size_t)(px - ix0) * 4;
|
|
167
|
+
if (cx >= 0.999f) {
|
|
168
|
+
std::memcpy(pixel, opaque_bytes, 4);
|
|
169
|
+
} else {
|
|
170
|
+
float color[4];
|
|
171
|
+
copy_color(solid_color, color);
|
|
172
|
+
blend_pixel(pixel, color, cx, alpha);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
212
177
|
for (int px = ix0; px < ix1; px++) {
|
|
213
178
|
float cx = std::min(x1, (float)(px + 1)) - std::max(x0, (float)px);
|
|
214
179
|
if (cx <= 0.0f) continue;
|
|
215
180
|
float color[4];
|
|
216
|
-
|
|
217
|
-
|
|
181
|
+
if (solid) {
|
|
182
|
+
copy_color(solid_color, color);
|
|
183
|
+
} else {
|
|
184
|
+
paint_color_at(paint, (float)px + 0.5f, (float)py + 0.5f, color);
|
|
185
|
+
}
|
|
186
|
+
blend_clipped(dst + (size_t)(px - ix0) * 4, color, cx * cy, alpha, paint, clip, px, py);
|
|
218
187
|
}
|
|
219
188
|
}
|
|
220
189
|
}
|
|
221
190
|
|
|
222
191
|
void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
223
|
-
float x1, float y1) {
|
|
192
|
+
float x1, float y1, const CanvasClipMask *clip) {
|
|
224
193
|
if (x1 < x0) std::swap(x0, x1);
|
|
225
194
|
if (y1 < y0) std::swap(y0, y1);
|
|
195
|
+
if (clip == nullptr && x0 <= 0.0f && y0 <= 0.0f && x1 >= (float)width &&
|
|
196
|
+
y1 >= (float)height) {
|
|
197
|
+
std::fill(pixels.begin(), pixels.end(), (unsigned char)0);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
226
200
|
int ix0 = std::max(0, (int)std::floor(x0));
|
|
227
201
|
int ix1 = std::min(width, (int)std::ceil(x1));
|
|
228
202
|
int iy0 = std::max(0, (int)std::floor(y0));
|
|
@@ -235,7 +209,9 @@ void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float
|
|
|
235
209
|
float cx = std::min(x1, (float)(px + 1)) - std::max(x0, (float)px);
|
|
236
210
|
if (cx <= 0.0f) continue;
|
|
237
211
|
unsigned char *pixel = dst + (size_t)(px - ix0) * 4;
|
|
238
|
-
float
|
|
212
|
+
float amount = cx * cy;
|
|
213
|
+
if (clip != nullptr) amount *= clip->at(px, py) / 255.0f;
|
|
214
|
+
float keep = 1.0f - amount;
|
|
239
215
|
if (keep <= 0.0f) {
|
|
240
216
|
pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
|
|
241
217
|
} else {
|
|
@@ -245,9 +221,55 @@ void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float
|
|
|
245
221
|
}
|
|
246
222
|
}
|
|
247
223
|
|
|
224
|
+
/** Sample one source image pixel at continuous coordinates (`0..width`).
|
|
225
|
+
* `smooth` selects bilinear filtering, otherwise the containing texel is used. */
|
|
226
|
+
static void sample_source(const Image &source, float fx, float fy, bool smooth, float out[4]) {
|
|
227
|
+
auto texel_at = [&](int x, int y, float outPixel[4]) {
|
|
228
|
+
if (x < 0) x = 0;
|
|
229
|
+
if (y < 0) y = 0;
|
|
230
|
+
if (x >= source.width) x = source.width - 1;
|
|
231
|
+
if (y >= source.height) y = source.height - 1;
|
|
232
|
+
const unsigned char *texel =
|
|
233
|
+
source.pixels.data() + ((size_t)y * (size_t)source.width + (size_t)x) * 4;
|
|
234
|
+
for (int i = 0; i < 4; i++) outPixel[i] = texel[i] / 255.0f;
|
|
235
|
+
};
|
|
236
|
+
if (!smooth) {
|
|
237
|
+
texel_at((int)fx, (int)fy, out);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const float x = fx - 0.5f;
|
|
241
|
+
const float y = fy - 0.5f;
|
|
242
|
+
int x0 = (int)std::floor(x);
|
|
243
|
+
int y0 = (int)std::floor(y);
|
|
244
|
+
const float tx = x - (float)x0;
|
|
245
|
+
const float ty = y - (float)y0;
|
|
246
|
+
float top[4];
|
|
247
|
+
float bottom[4];
|
|
248
|
+
float a[4];
|
|
249
|
+
float b[4];
|
|
250
|
+
texel_at(x0, y0, a);
|
|
251
|
+
texel_at(x0 + 1, y0, b);
|
|
252
|
+
for (int i = 0; i < 4; i++) top[i] = a[i] + (b[i] - a[i]) * tx;
|
|
253
|
+
texel_at(x0, y0 + 1, a);
|
|
254
|
+
texel_at(x0 + 1, y0 + 1, b);
|
|
255
|
+
for (int i = 0; i < 4; i++) bottom[i] = a[i] + (b[i] - a[i]) * tx;
|
|
256
|
+
for (int i = 0; i < 4; i++) out[i] = top[i] + (bottom[i] - top[i]) * ty;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Source pixel centre for a destination pixel of `drawImage`. */
|
|
260
|
+
static void draw_image_pixel(std::vector<unsigned char> &pixels, int width, const Image &source,
|
|
261
|
+
const CanvasPaint &paint, int px, int py, float u, float v, float sx,
|
|
262
|
+
float sy, float sw, float sh, float alpha, bool smooth,
|
|
263
|
+
const CanvasClipMask *clip) {
|
|
264
|
+
float sampled[4];
|
|
265
|
+
sample_source(source, sx + u * sw, sy + v * sh, smooth, sampled);
|
|
266
|
+
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4;
|
|
267
|
+
blend_clipped(dst, sampled, 1.0f, alpha, paint, clip, px, py);
|
|
268
|
+
}
|
|
269
|
+
|
|
248
270
|
void draw_image(std::vector<unsigned char> &pixels, int width, int height, const Image &source,
|
|
249
271
|
float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh,
|
|
250
|
-
float alpha) {
|
|
272
|
+
float alpha, bool smooth, const CanvasClipMask *clip) {
|
|
251
273
|
if (source.width <= 0 || source.height <= 0 || dw <= 0.0f || dh <= 0.0f || sw <= 0.0f ||
|
|
252
274
|
sh <= 0.0f)
|
|
253
275
|
return;
|
|
@@ -255,30 +277,46 @@ void draw_image(std::vector<unsigned char> &pixels, int width, int height, const
|
|
|
255
277
|
int ix1 = std::min(width, (int)std::ceil(dx + dw));
|
|
256
278
|
int iy0 = std::max(0, (int)std::floor(dy));
|
|
257
279
|
int iy1 = std::min(height, (int)std::ceil(dy + dh));
|
|
280
|
+
const CanvasPaint paint;
|
|
281
|
+
/* A 1:1 blit at integer offsets samples exactly one texel per pixel: skip the
|
|
282
|
+
* per-pixel divide and the four bilinear taps. Sprite/offscreen-canvas
|
|
283
|
+
* compositing in a multi-element scene is usually this case. */
|
|
284
|
+
const int dx_int = (int)std::lround(dx);
|
|
285
|
+
const int dy_int = (int)std::lround(dy);
|
|
286
|
+
const int sx_int = (int)std::lround(sx);
|
|
287
|
+
const int sy_int = (int)std::lround(sy);
|
|
288
|
+
const bool one_to_one = std::fabs(dw - sw) < 0.001f && std::fabs(dh - sh) < 0.001f &&
|
|
289
|
+
std::fabs(dx - (float)dx_int) < 0.001f &&
|
|
290
|
+
std::fabs(dy - (float)dy_int) < 0.001f &&
|
|
291
|
+
std::fabs(sx - (float)sx_int) < 0.001f &&
|
|
292
|
+
std::fabs(sy - (float)sy_int) < 0.001f;
|
|
293
|
+
if (one_to_one) {
|
|
294
|
+
for (int py = iy0; py < iy1; py++) {
|
|
295
|
+
int texelY = sy_int + (py - dy_int);
|
|
296
|
+
if (texelY < 0) texelY = 0;
|
|
297
|
+
if (texelY >= source.height) texelY = source.height - 1;
|
|
298
|
+
const unsigned char *source_row =
|
|
299
|
+
source.pixels.data() + (size_t)texelY * (size_t)source.width * 4;
|
|
300
|
+
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)ix0) * 4;
|
|
301
|
+
for (int px = ix0; px < ix1; px++) {
|
|
302
|
+
int texelX = sx_int + (px - dx_int);
|
|
303
|
+
if (texelX < 0) texelX = 0;
|
|
304
|
+
if (texelX >= source.width) texelX = source.width - 1;
|
|
305
|
+
const unsigned char *texel = source_row + (size_t)texelX * 4;
|
|
306
|
+
float sampled[4] = {texel[0] / 255.0f, texel[1] / 255.0f, texel[2] / 255.0f,
|
|
307
|
+
texel[3] / 255.0f};
|
|
308
|
+
blend_clipped(dst + (size_t)(px - ix0) * 4, sampled, 1.0f, alpha, paint, clip, px, py);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
258
313
|
for (int py = iy0; py < iy1; py++) {
|
|
259
314
|
for (int px = ix0; px < ix1; px++) {
|
|
260
315
|
float u = ((float)px + 0.5f - dx) / dw;
|
|
261
316
|
float v = ((float)py + 0.5f - dy) / dh;
|
|
262
317
|
if (u < 0.0f || u >= 1.0f || v < 0.0f || v >= 1.0f) continue;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (sxi < 0) sxi = 0;
|
|
266
|
-
if (syi < 0) syi = 0;
|
|
267
|
-
if (sxi >= source.width) sxi = source.width - 1;
|
|
268
|
-
if (syi >= source.height) syi = source.height - 1;
|
|
269
|
-
const unsigned char *texel =
|
|
270
|
-
source.pixels.data() + ((size_t)syi * (size_t)source.width + (size_t)sxi) * 4;
|
|
271
|
-
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4;
|
|
272
|
-
if (alpha >= 0.999f) {
|
|
273
|
-
const unsigned char ta = texel[3];
|
|
274
|
-
if (ta == 255) {
|
|
275
|
-
dst[0] = texel[0]; dst[1] = texel[1]; dst[2] = texel[2]; dst[3] = 255;
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
if (ta == 0) continue;
|
|
279
|
-
}
|
|
280
|
-
float color[4] = {texel[0] / 255.0f, texel[1] / 255.0f, texel[2] / 255.0f, texel[3] / 255.0f};
|
|
281
|
-
blend_pixel(dst, color, 1.0f, alpha);
|
|
318
|
+
draw_image_pixel(pixels, width, source, paint, px, py, u, v, sx, sy, sw, sh, alpha, smooth,
|
|
319
|
+
clip);
|
|
282
320
|
}
|
|
283
321
|
}
|
|
284
322
|
}
|
|
@@ -287,7 +325,7 @@ void draw_image(std::vector<unsigned char> &pixels, int width, int height, const
|
|
|
287
325
|
void draw_image_affine(std::vector<unsigned char> &pixels, int width, int height,
|
|
288
326
|
const Image &source, float dx, float dy, float dw, float dh, float sx,
|
|
289
327
|
float sy, float sw, float sh, const CanvasTransform &transform,
|
|
290
|
-
float alpha) {
|
|
328
|
+
float alpha, bool smooth, const CanvasClipMask *clip) {
|
|
291
329
|
if (source.width <= 0 || source.height <= 0 || dw <= 0.0f || dh <= 0.0f || sw <= 0.0f ||
|
|
292
330
|
sh <= 0.0f)
|
|
293
331
|
return;
|
|
@@ -312,6 +350,7 @@ void draw_image_affine(std::vector<unsigned char> &pixels, int width, int height
|
|
|
312
350
|
int ix1 = std::min(width, (int)std::ceil(maxX));
|
|
313
351
|
int iy0 = std::max(0, (int)std::floor(minY));
|
|
314
352
|
int iy1 = std::min(height, (int)std::ceil(maxY));
|
|
353
|
+
const CanvasPaint paint;
|
|
315
354
|
for (int py = iy0; py < iy1; py++) {
|
|
316
355
|
for (int px = ix0; px < ix1; px++) {
|
|
317
356
|
float ux = 0.0f;
|
|
@@ -320,25 +359,8 @@ void draw_image_affine(std::vector<unsigned char> &pixels, int width, int height
|
|
|
320
359
|
float u = (ux - dx) / dw;
|
|
321
360
|
float v = (uy - dy) / dh;
|
|
322
361
|
if (u < 0.0f || u >= 1.0f || v < 0.0f || v >= 1.0f) continue;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (sxi < 0) sxi = 0;
|
|
326
|
-
if (syi < 0) syi = 0;
|
|
327
|
-
if (sxi >= source.width) sxi = source.width - 1;
|
|
328
|
-
if (syi >= source.height) syi = source.height - 1;
|
|
329
|
-
const unsigned char *texel =
|
|
330
|
-
source.pixels.data() + ((size_t)syi * (size_t)source.width + (size_t)sxi) * 4;
|
|
331
|
-
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4;
|
|
332
|
-
if (alpha >= 0.999f) {
|
|
333
|
-
const unsigned char ta = texel[3];
|
|
334
|
-
if (ta == 255) {
|
|
335
|
-
dst[0] = texel[0]; dst[1] = texel[1]; dst[2] = texel[2]; dst[3] = 255;
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
if (ta == 0) continue;
|
|
339
|
-
}
|
|
340
|
-
float color[4] = {texel[0] / 255.0f, texel[1] / 255.0f, texel[2] / 255.0f, texel[3] / 255.0f};
|
|
341
|
-
blend_pixel(dst, color, 1.0f, alpha);
|
|
362
|
+
draw_image_pixel(pixels, width, source, paint, px, py, u, v, sx, sy, sw, sh, alpha, smooth,
|
|
363
|
+
clip);
|
|
342
364
|
}
|
|
343
365
|
}
|
|
344
366
|
}
|
|
@@ -346,7 +368,7 @@ void draw_image_affine(std::vector<unsigned char> &pixels, int width, int height
|
|
|
346
368
|
void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
347
369
|
const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
|
|
348
370
|
int top, float x, float baselineY, float scale_x, const CanvasPaint &paint,
|
|
349
|
-
float alpha) {
|
|
371
|
+
float alpha, const CanvasClipMask *clip) {
|
|
350
372
|
if (glyphWidth <= 0 || glyphHeight <= 0) return;
|
|
351
373
|
if (scale_x <= 0.0f) scale_x = 1.0f;
|
|
352
374
|
bool condense = scale_x < 0.999f;
|
|
@@ -354,6 +376,9 @@ void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
|
354
376
|
: glyphWidth;
|
|
355
377
|
int originX = (int)std::lround(x) + (int)std::lround((float)left * scale_x);
|
|
356
378
|
int originY = (int)std::lround(baselineY) - top;
|
|
379
|
+
const bool solid = paint_is_solid(paint);
|
|
380
|
+
float solid_color[4];
|
|
381
|
+
if (solid) copy_color(paint.color, solid_color);
|
|
357
382
|
for (int gy = 0; gy < glyphHeight; gy++) {
|
|
358
383
|
int py = originY + gy;
|
|
359
384
|
if (py < 0 || py >= height) continue;
|
|
@@ -365,9 +390,13 @@ void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
|
365
390
|
float coverage = glyph[(size_t)gy * (size_t)glyphWidth + (size_t)sourceX] / 255.0f;
|
|
366
391
|
if (coverage <= 0.0f) continue;
|
|
367
392
|
float color[4];
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
393
|
+
if (solid) {
|
|
394
|
+
copy_color(solid_color, color);
|
|
395
|
+
} else {
|
|
396
|
+
paint_color_at(paint, (float)px + 0.5f, (float)py + 0.5f, color);
|
|
397
|
+
}
|
|
398
|
+
blend_clipped(pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4, color,
|
|
399
|
+
coverage, alpha, paint, clip, px, py);
|
|
371
400
|
}
|
|
372
401
|
}
|
|
373
402
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — Canvas2D shadows and clip-region compositing.
|
|
3
|
+
*
|
|
4
|
+
* A drawing operation that has a visible `shadowColor` is rendered into a
|
|
5
|
+
* scratch layer first, so the shadow is derived from exactly the pixels the
|
|
6
|
+
* operation covers (including glyph coverage and image alpha). The layer's
|
|
7
|
+
* alpha is offset and box-blurred, tinted with `shadowColor`, composited, and
|
|
8
|
+
* only then is the operation's own colour composited on top.
|
|
9
|
+
*
|
|
10
|
+
* The scratch layer is deliberately drawn *without* the clip mask: the shadow
|
|
11
|
+
* must be able to leave the clip region and be clipped once, at composite time.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#include "canvas_internal.h"
|
|
15
|
+
|
|
16
|
+
#include <algorithm>
|
|
17
|
+
#include <cmath>
|
|
18
|
+
#include <utility>
|
|
19
|
+
#include <vector>
|
|
20
|
+
|
|
21
|
+
namespace xtgui {
|
|
22
|
+
|
|
23
|
+
namespace {
|
|
24
|
+
|
|
25
|
+
void box_blur_pass(std::vector<float> &source, std::vector<float> &temp, int width, int height,
|
|
26
|
+
int radius) {
|
|
27
|
+
if (radius <= 0) return;
|
|
28
|
+
const float scale = 1.0f / (float)(radius * 2 + 1);
|
|
29
|
+
temp.resize(source.size());
|
|
30
|
+
/* Horizontal. */
|
|
31
|
+
for (int y = 0; y < height; y++) {
|
|
32
|
+
const float *row = source.data() + (size_t)y * (size_t)width;
|
|
33
|
+
float *out = temp.data() + (size_t)y * (size_t)width;
|
|
34
|
+
float sum = 0.0f;
|
|
35
|
+
for (int x = -radius; x <= radius; x++) sum += row[std::min(width - 1, std::max(0, x))];
|
|
36
|
+
for (int x = 0; x < width; x++) {
|
|
37
|
+
out[x] = sum * scale;
|
|
38
|
+
const int remove = std::min(width - 1, std::max(0, x - radius));
|
|
39
|
+
const int add = std::min(width - 1, std::max(0, x + radius + 1));
|
|
40
|
+
sum += row[add] - row[remove];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/* Vertical. */
|
|
44
|
+
for (int x = 0; x < width; x++) {
|
|
45
|
+
float sum = 0.0f;
|
|
46
|
+
for (int y = -radius; y <= radius; y++) {
|
|
47
|
+
sum += temp[(size_t)std::min(height - 1, std::max(0, y)) * (size_t)width + (size_t)x];
|
|
48
|
+
}
|
|
49
|
+
for (int y = 0; y < height; y++) {
|
|
50
|
+
source[(size_t)y * (size_t)width + (size_t)x] = sum * scale;
|
|
51
|
+
const int remove = std::min(height - 1, std::max(0, y - radius));
|
|
52
|
+
const int add = std::min(height - 1, std::max(0, y + radius + 1));
|
|
53
|
+
sum += temp[(size_t)add * (size_t)width + (size_t)x] -
|
|
54
|
+
temp[(size_t)remove * (size_t)width + (size_t)x];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
} // namespace
|
|
60
|
+
|
|
61
|
+
bool Canvas2D::shadow_active() const {
|
|
62
|
+
if (in_shadow_layer_) return false;
|
|
63
|
+
return state_.shadow_color[3] > 0.0f;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const CanvasClipMask *Canvas2D::clip_mask() const {
|
|
67
|
+
/* The scratch shadow layer is painted unclipped; the mask is applied when the
|
|
68
|
+
* finished layer is composited back. */
|
|
69
|
+
if (in_shadow_layer_ || !state_.clip) return nullptr;
|
|
70
|
+
return state_.clip.get();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
bool Canvas2D::begin_shadow_layer() {
|
|
74
|
+
if (in_shadow_layer_ || !shadow_active()) return false;
|
|
75
|
+
shadow_layer_.width = image_.width;
|
|
76
|
+
shadow_layer_.height = image_.height;
|
|
77
|
+
shadow_layer_.pixels.assign((size_t)image_.width * (size_t)image_.height * 4, 0);
|
|
78
|
+
std::swap(shadow_layer_.pixels, image_.pixels);
|
|
79
|
+
in_shadow_layer_ = true;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void Canvas2D::end_shadow_layer() {
|
|
84
|
+
if (!in_shadow_layer_) return;
|
|
85
|
+
in_shadow_layer_ = false;
|
|
86
|
+
std::swap(image_.pixels, shadow_layer_.pixels);
|
|
87
|
+
composite_shadow_layer(shadow_layer_);
|
|
88
|
+
composite_layer(shadow_layer_);
|
|
89
|
+
/* The shadow can reach beyond the draw's own bounds (offset + blur), so the
|
|
90
|
+
* conservative upload region is the whole canvas. */
|
|
91
|
+
mark_all_dirty();
|
|
92
|
+
generation_++;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void Canvas2D::composite_layer(const Image &layer) {
|
|
96
|
+
if (layer.width != image_.width || layer.height != image_.height) return;
|
|
97
|
+
const CanvasClipMask *clip = clip_mask();
|
|
98
|
+
for (int y = 0; y < layer.height; y++) {
|
|
99
|
+
const unsigned char *src = layer.pixels.data() + (size_t)y * (size_t)layer.width * 4;
|
|
100
|
+
unsigned char *dst = image_.pixels.data() + (size_t)y * (size_t)image_.width * 4;
|
|
101
|
+
for (int x = 0; x < layer.width; x++) {
|
|
102
|
+
const unsigned char *texel = src + (size_t)x * 4;
|
|
103
|
+
if (texel[3] == 0) continue;
|
|
104
|
+
float color[4] = {texel[0] / 255.0f, texel[1] / 255.0f, texel[2] / 255.0f,
|
|
105
|
+
texel[3] / 255.0f};
|
|
106
|
+
float coverage = clip != nullptr ? clip->at(x, y) / 255.0f : 1.0f;
|
|
107
|
+
if (coverage <= 0.0f) continue;
|
|
108
|
+
canvas_detail::blend_pixel_mode(dst + (size_t)x * 4, color, coverage, 1.0f,
|
|
109
|
+
state_.global_composite);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void Canvas2D::composite_shadow_layer(const Image &layer) {
|
|
115
|
+
if (layer.width != image_.width || layer.height != image_.height) return;
|
|
116
|
+
if (state_.shadow_color[3] <= 0.0f) return;
|
|
117
|
+
const int width = layer.width;
|
|
118
|
+
const int height = layer.height;
|
|
119
|
+
std::vector<float> alpha((size_t)width * (size_t)height, 0.0f);
|
|
120
|
+
for (int y = 0; y < height; y++) {
|
|
121
|
+
const unsigned char *row = layer.pixels.data() + (size_t)y * (size_t)width * 4;
|
|
122
|
+
float *out = alpha.data() + (size_t)y * (size_t)width;
|
|
123
|
+
for (int x = 0; x < width; x++) out[x] = row[(size_t)x * 4 + 3] / 255.0f;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* `shadowBlur` is the diameter of the blur; three box passes approximate a
|
|
127
|
+
* Gaussian closely enough for UI work and stay O(width * height). */
|
|
128
|
+
const float blur = std::isfinite(state_.shadow_blur) ? state_.shadow_blur : 0.0f;
|
|
129
|
+
if (blur > 0.0f) {
|
|
130
|
+
int radius = (int)std::lround(blur * 0.5f);
|
|
131
|
+
if (radius < 1) radius = 1;
|
|
132
|
+
if (radius > 64) radius = 64;
|
|
133
|
+
std::vector<float> temp;
|
|
134
|
+
for (int pass = 0; pass < 3; pass++) box_blur_pass(alpha, temp, width, height, radius);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const int offsetX = (int)std::lround(state_.shadow_offset_x);
|
|
138
|
+
const int offsetY = (int)std::lround(state_.shadow_offset_y);
|
|
139
|
+
const CanvasClipMask *clip = clip_mask();
|
|
140
|
+
const float shadowAlpha = state_.shadow_color[3];
|
|
141
|
+
for (int y = 0; y < height; y++) {
|
|
142
|
+
const int sourceY = y - offsetY;
|
|
143
|
+
if (sourceY < 0 || sourceY >= height) continue;
|
|
144
|
+
const float *row = alpha.data() + (size_t)sourceY * (size_t)width;
|
|
145
|
+
unsigned char *dst = image_.pixels.data() + (size_t)y * (size_t)image_.width * 4;
|
|
146
|
+
for (int x = 0; x < width; x++) {
|
|
147
|
+
const int sourceX = x - offsetX;
|
|
148
|
+
if (sourceX < 0 || sourceX >= width) continue;
|
|
149
|
+
const float coverage = row[sourceX];
|
|
150
|
+
if (coverage <= 0.0f) continue;
|
|
151
|
+
float color[4] = {state_.shadow_color[0], state_.shadow_color[1], state_.shadow_color[2],
|
|
152
|
+
shadowAlpha};
|
|
153
|
+
float clipCoverage = clip != nullptr ? clip->at(x, y) / 255.0f : 1.0f;
|
|
154
|
+
if (clipCoverage <= 0.0f) continue;
|
|
155
|
+
canvas_detail::blend_pixel_mode(dst + (size_t)x * 4, color, coverage * clipCoverage, 1.0f,
|
|
156
|
+
kCanvasCompositeSourceOver);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
} // namespace xtgui
|