Skip to content

Commit d6e43b2

Browse files
committed
Support custom theme (#156)
1 parent 6d9162f commit d6e43b2

36 files changed

Lines changed: 660 additions & 243 deletions

integrations/runner.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,16 @@ process.stdin.on('end', () => {
172172
// Set cropbox settings
173173
const originalScale = scene.scale;
174174
const originalOrigin = { ...scene.origin };
175+
const originalBackgroundColor = { ...scene.theme.background.color };
175176

176177
scene.scale = cropBox.width / (cropBox.p4.x - cropBox.p1.x);
177178
scene.origin = { x: -cropBox.p1.x * scene.scale, y: -cropBox.p1.y * scene.scale };
178-
179+
180+
if (cropBox.transparent && scene.theme.background.color.r == 0 && scene.theme.background.color.g == 0 && scene.theme.background.color.b == 0) {
181+
// Use a non-black background color so that some rendering (e.g. glass) will not assume the background is black.
182+
scene.theme.background.color = { r: 0.01, g: 0.01, b: 0.01 };
183+
}
184+
179185
// Create simulator with canvas
180186
const simulator = new rayOptics.Simulator(
181187
scene,
@@ -191,8 +197,11 @@ process.stdin.on('end', () => {
191197

192198
simulator.on('simulationComplete', () => {
193199
// Draw layers to final canvas
194-
ctxFinal.fillStyle = 'black';
195-
ctxFinal.fillRect(0, 0, canvasFinal.width, canvasFinal.height);
200+
201+
if (!cropBox.transparent) {
202+
ctxFinal.fillStyle = `rgb(${Math.round(scene.theme.background.color.r * 255)}, ${Math.round(scene.theme.background.color.g * 255)}, ${Math.round(scene.theme.background.color.b * 255)})`;
203+
ctxFinal.fillRect(0, 0, canvasFinal.width, canvasFinal.height);
204+
}
196205

197206
ctxFinal.drawImage(canvasBelowLight, 0, 0);
198207
ctxFinal.drawImage(canvasGrid, 0, 0);
@@ -208,7 +217,8 @@ process.stdin.on('end', () => {
208217
// Restore original scene settings
209218
scene.scale = originalScale;
210219
scene.origin = originalOrigin;
211-
220+
scene.theme.background.color = originalBackgroundColor;
221+
212222
// Add simulator statistics
213223
result.totalTruncation = simulator.totalTruncation;
214224
result.processedRayCount = simulator.processedRayCount;

scripts/buildImages.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ function exportImageFromCropBox(cropBox, filename, skipLight, callback) {
149149
initSimulatorForCropBox(cropBox, skipLight);
150150
simulate(skipLight, function () {
151151
// Clear final canvas
152-
ctxFinal.fillStyle = 'black';
152+
ctxFinal.fillStyle = `rgb(${Math.round(scene.theme.background.color.r * 255)}, ${Math.round(scene.theme.background.color.g * 255)}, ${Math.round(scene.theme.background.color.b * 255)})`;
153153
ctxFinal.fillRect(0, 0, canvasFinal.width, canvasFinal.height);
154154

155155
// Draw the layers

src/app/services/app.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ function initAppService() {
125125

126126
simulator.dpr = dpr;
127127

128+
simulator.on('update', function () {
129+
canvasBelowLight.style.backgroundColor = `rgb(${Math.round(scene.theme.background.color.r * 255)}, ${Math.round(scene.theme.background.color.g * 255)}, ${Math.round(scene.theme.background.color.b * 255)})`;
130+
});
131+
128132
simulator.on('simulationStart', function () {
129133
statusEmitter.emit(STATUS_EVENT_NAMES.SIMULATOR_STATUS, {
130134
rayCount: 0,

src/core/CanvasRenderer.js

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,23 @@ class CanvasRenderer {
6464

6565
/**
6666
* Converts an RGBA array [R, G, B, A] with values between 0 and 1 to a CSS color string.
67-
* @param {number[]} rgba - The RGBA array.
67+
* @param {number[]|object} rgba - The RGBA array or object with r, g, b, a properties.
6868
* @returns {string} The CSS color string.
6969
*/
7070
rgbaToCssColor(rgba) {
71-
const [r, g, b, a] = rgba;
72-
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
71+
if (rgba.r !== undefined) {
72+
const { r, g, b, a } = rgba;
73+
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
74+
} else {
75+
const [r, g, b, a] = rgba;
76+
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
77+
}
7378
}
7479

7580
/**
7681
* Draw a point.
7782
* @param {Point} p
78-
* @param {number[]} [color=[0, 0, 0, 1]]
83+
* @param {number[]|object} [color=[0, 0, 0, 1]]
7984
* @param {number} [size=5]
8085
*/
8186
drawPoint(p, color = [0, 0, 0, 1], size = 5) {
@@ -86,11 +91,15 @@ class CanvasRenderer {
8691
/**
8792
* Draw a line.
8893
* @param {Line} l
89-
* @param {number[]} [color=[0, 0, 0, 1]]
94+
* @param {number[]|object} [color=[0, 0, 0, 1]]
95+
* @param {boolean} [showArrow=false] (not implemented, just for parameter consistency)
96+
* @param {number[]} [lineDash=[]]
97+
* @param {number} [lineWidth=1]
9098
*/
91-
drawLine(l, color = [0, 0, 0, 1]) {
99+
drawLine(l, color = [0, 0, 0, 1], showArrow = false, lineDash = [], lineWidth = 1) {
100+
this.ctx.setLineDash(lineDash.map(value => value * this.lengthScale));
92101
this.ctx.strokeStyle = this.rgbaToCssColor(color);
93-
this.ctx.lineWidth = 1 * this.lengthScale;
102+
this.ctx.lineWidth = lineWidth * this.lengthScale;
94103
this.ctx.beginPath();
95104
let ang1 = Math.atan2((l.p2.x - l.p1.x), (l.p2.y - l.p1.y));
96105
let cvsLimit = (Math.abs(l.p1.x + this.origin.x) + Math.abs(l.p1.y + this.origin.y) + this.canvas.height + this.canvas.width) / Math.min(1, this.scale);
@@ -102,14 +111,15 @@ class CanvasRenderer {
102111
/**
103112
* Draw a ray.
104113
* @param {Line} r
105-
* @param {number[]} [color=[0, 0, 0, 1]]
114+
* @param {number[]|object} [color=[0, 0, 0, 1]]
106115
* @param {boolean} [showArrow=true]
107116
* @param {number[]} [lineDash=[]]
117+
* @param {number} [lineWidth=1]
108118
*/
109-
drawRay(r, color = [0, 0, 0, 1], showArrow = false, lineDash = []) {
110-
this.ctx.setLineDash(lineDash);
119+
drawRay(r, color = [0, 0, 0, 1], showArrow = false, lineDash = [], lineWidth = 1) {
120+
this.ctx.setLineDash(lineDash.map(value => value * this.lengthScale));
111121
this.ctx.strokeStyle = this.rgbaToCssColor(color);
112-
this.ctx.lineWidth = 1 * this.lengthScale;
122+
this.ctx.lineWidth = lineWidth * this.lengthScale;
113123
this.ctx.fillStyle = this.rgbaToCssColor(color);
114124

115125
// Check if ray has a valid direction
@@ -190,14 +200,15 @@ class CanvasRenderer {
190200
/**
191201
* Draw a segment.
192202
* @param {Line} s
193-
* @param {number[]} [color=[0, 0, 0, 1]]
203+
* @param {number[]|object} [color=[0, 0, 0, 1]]
194204
* @param {boolean} [showArrow=true]
195-
* @param {number} [arrowPosition=0.67] Position of arrow along line (0 to 1, where 0 is at p1 and 1 is at p2)
205+
* @param {number[]} [lineDash=[]]
206+
* @param {number} [lineWidth=1]
196207
*/
197-
drawSegment(s, color = [0, 0, 0, 1], showArrow = false, lineDash = []) {
198-
this.ctx.setLineDash(lineDash);
208+
drawSegment(s, color = [0, 0, 0, 1], showArrow = false, lineDash = [], lineWidth = 1) {
209+
this.ctx.setLineDash(lineDash.map(value => value * this.lengthScale));
199210
this.ctx.strokeStyle = this.rgbaToCssColor(color);
200-
this.ctx.lineWidth = 1 * this.lengthScale;
211+
this.ctx.lineWidth = lineWidth * this.lengthScale;
201212
this.ctx.fillStyle = this.rgbaToCssColor(color);
202213

203214
// Calculate arrow size first to determine if we should draw it
@@ -272,7 +283,7 @@ class CanvasRenderer {
272283
/**
273284
* Draw a circle.
274285
* @param {Circle} c
275-
* @param {String} [color='black']
286+
* @param {String|object} [color='black']
276287
*/
277288
drawCircle(c, color = 'black') {
278289
this.ctx.strokeStyle = color;

src/core/Editor.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,8 +1347,11 @@ class Editor {
13471347
const imageHeight = cropBox.p4.y - cropBox.p1.y;
13481348

13491349
const ctxSVG = new C2S(imageWidth, imageHeight);
1350-
ctxSVG.fillStyle = "black";
1351-
ctxSVG.fillRect(0, 0, imageWidth, imageHeight);
1350+
1351+
if (!cropBox.transparent) {
1352+
ctxSVG.fillStyle = `rgb(${Math.round(exportingScene.theme.background.color.r * 255)}, ${Math.round(exportingScene.theme.background.color.g * 255)}, ${Math.round(exportingScene.theme.background.color.b * 255)})`;
1353+
ctxSVG.fillRect(0, 0, imageWidth, imageHeight);
1354+
}
13521355

13531356
const exportSimulator = new Simulator(exportingScene, ctxSVG, null, null, null, null, false, cropBox.rayCountLimit || 1e7);
13541357

@@ -1380,6 +1383,11 @@ class Editor {
13801383
exportingScene.scale = cropBox.width / (cropBox.p4.x - cropBox.p1.x);
13811384
exportingScene.origin = { x: -cropBox.p1.x * exportingScene.scale, y: -cropBox.p1.y * exportingScene.scale };
13821385

1386+
if (cropBox.transparent && exportingScene.theme.background.color.r == 0 && exportingScene.theme.background.color.g == 0 && exportingScene.theme.background.color.b == 0) {
1387+
// Use a non-black background color so that some rendering (e.g. glass) will not assume the background is black.
1388+
exportingScene.theme.background.color = { r: 0.01, g: 0.01, b: 0.01 };
1389+
}
1390+
13831391
const imageWidth = cropBox.width;
13841392
const imageHeight = cropBox.width * (cropBox.p4.y - cropBox.p1.y) / (cropBox.p4.x - cropBox.p1.x);
13851393

@@ -1415,8 +1423,12 @@ class Editor {
14151423
finalCanvas.width = imageWidth;
14161424
finalCanvas.height = imageHeight;
14171425
const finalCtx = finalCanvas.getContext('2d');
1418-
finalCtx.fillStyle = "black";
1419-
finalCtx.fillRect(0, 0, cropBox.width, cropBox.width * (cropBox.p4.y - cropBox.p1.y) / (cropBox.p4.x - cropBox.p1.x));
1426+
1427+
if (!cropBox.transparent) {
1428+
finalCtx.fillStyle = `rgb(${Math.round(exportingScene.theme.background.color.r * 255)}, ${Math.round(exportingScene.theme.background.color.g * 255)}, ${Math.round(exportingScene.theme.background.color.b * 255)})`;
1429+
finalCtx.fillRect(0, 0, cropBox.width, cropBox.width * (cropBox.p4.y - cropBox.p1.y) / (cropBox.p4.x - cropBox.p1.x));
1430+
}
1431+
14201432
finalCtx.drawImage(canvases[1], 0, 0);
14211433
finalCtx.drawImage(canvases[3], 0, 0);
14221434
if (self.scene.colorMode == 'default') {

0 commit comments

Comments
 (0)