cloudmr-ux 4.4.9 → 4.5.2

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.
@@ -1,14 +1,16 @@
1
1
  import React from "react";
2
2
  import "./Toolbar.css";
3
3
  import type { DrawToolkitProps } from "../draw-toolkit/DrawToolkit";
4
- /** Props for {@link DrawToolkit} minus brush controls when using {@link MroDrawToolkit}. */
5
- export type CloudMrDrawToolkitProps = Omit<DrawToolkitProps, "brushSize" | "updateBrushSize"> & {
4
+ /** Props for {@link MroDrawToolkit} extends draw toolkit with MRO pen/shape controls. */
5
+ export type CloudMrDrawToolkitProps = Omit<DrawToolkitProps, "rois" | "selectedROI" | "setSelectedROI" | "saveROI" | "labelsVisible" | "toggleLabelsVisible"> & {
6
6
  penDrawMode?: "freehand" | "polyline";
7
7
  onPenDrawModeChange?: (mode: "freehand" | "polyline") => void;
8
8
  polylineVertexCount?: number;
9
9
  onCancelPolyline?: () => void;
10
- onFinishPolyline?: () => void;
11
- onCloseFillPolyline?: () => void;
10
+ onApplyPenDraft?: () => void;
11
+ onCancelPenDraft?: () => void;
12
+ onCloseFillPenDraft?: () => void;
13
+ penDraftActive?: boolean;
12
14
  shapeDraftActive?: boolean;
13
15
  onApplyShapeDraft?: () => void;
14
16
  onCancelShapeDraft?: () => void;
@@ -48,5 +50,24 @@ export interface CloudMrNiivuePanelProps {
48
50
  onShapeDraftChange: (draft: import("./shapeDraftUtils").ShapeDraft) => void;
49
51
  onApplyShapeDraft: () => void;
50
52
  onCancelShapeDraft: () => void;
53
+ penDraft: {
54
+ kind: "polyline" | "freehand";
55
+ baseBitmap: Uint8Array;
56
+ axCorSag: number;
57
+ penValue: number;
58
+ vertices?: [number, number, number][];
59
+ strokeVoxels?: [number, number, number][];
60
+ bounds?: {
61
+ x1: number;
62
+ y1: number;
63
+ x2: number;
64
+ y2: number;
65
+ z1: number;
66
+ z2: number;
67
+ };
68
+ } | null;
69
+ onPenDraftChange: (draft: NonNullable<CloudMrNiivuePanelProps["penDraft"]>) => void;
70
+ onApplyPenDraft: () => void;
71
+ onCancelPenDraft: () => void;
51
72
  }
52
73
  export declare function CloudMrNiivuePanel(props: CloudMrNiivuePanelProps): import("react/jsx-runtime").JSX.Element;
@@ -56,6 +56,7 @@ import { NiivueContrastAdjustments } from "../niivue-contrast-adjustments/Niivue
56
56
  import { NiivueRoiTable } from "../niivue-roi-table/NiivueRoiTable";
57
57
  import { MroDrawToolkit } from "./mro-draw-toolkit/MroDrawToolkit";
58
58
  import { ShapeDraftOverlay } from "./ShapeDraftOverlay";
59
+ import { PenDraftOverlay } from "./PenDraftOverlay";
59
60
  export function CloudMrNiivuePanel(props) {
60
61
  var _this = this;
61
62
  var canvas = React.useRef(null);
@@ -110,12 +111,17 @@ export function CloudMrNiivuePanel(props) {
110
111
  if (props.shapeDraft) {
111
112
  props.onCancelShapeDraft();
112
113
  }
114
+ if (props.penDraft) {
115
+ props.onCancelPenDraft();
116
+ }
113
117
  if (tool !== "pen") {
114
118
  (_b = (_a = props.drawToolkitProps).onPenDrawModeChange) === null || _b === void 0 ? void 0 : _b.call(_a, "freehand");
115
119
  }
116
120
  props.setDrawShapeTool(tool);
117
121
  var nv = props.nv;
118
122
  nv.opts.deferShapeCommit = tool === "rectangle" || tool === "ellipse";
123
+ nv.opts.deferFreehandCommit =
124
+ tool === "pen" && props.drawToolkitProps.penDrawMode === "freehand";
119
125
  nv.opts.penType =
120
126
  tool === "rectangle"
121
127
  ? NI_PEN_TYPE.RECTANGLE
@@ -150,8 +156,11 @@ export function CloudMrNiivuePanel(props) {
150
156
  if (props.shapeDraft) {
151
157
  props.onCancelShapeDraft();
152
158
  }
159
+ if (props.penDraft) {
160
+ props.onCancelPenDraft();
161
+ }
153
162
  props.setDrawShapeTool(null);
154
- }, shapeDraftActive: props.shapeDraft != null, onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft, style: {
163
+ }, shapeDraftActive: props.shapeDraft != null, penDraftActive: props.penDraft != null, onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft, style: {
155
164
  marginBottom: 0,
156
165
  width: "100%",
157
166
  flexShrink: 0
@@ -182,7 +191,7 @@ export function CloudMrNiivuePanel(props) {
182
191
  left: 0,
183
192
  width: "100%",
184
193
  height: "100%"
185
- } }), props.shapeDraft && (_jsx(ShapeDraftOverlay, { nv: props.nv, draft: props.shapeDraft, onDraftChange: props.onShapeDraftChange, overlayKey: props.mms }))] }))] })), _jsxs(Box, __assign({ sx: {
194
+ } }), props.shapeDraft && (_jsx(ShapeDraftOverlay, { nv: props.nv, draft: props.shapeDraft, onDraftChange: props.onShapeDraftChange, overlayKey: props.mms })), props.penDraft && (_jsx(PenDraftOverlay, { nv: props.nv, draft: props.penDraft, onDraftChange: props.onPenDraftChange, overlayKey: props.mms }))] }))] })), _jsxs(Box, __assign({ sx: {
186
195
  width: {
187
196
  xs: "100%",
188
197
  md: "35%"
@@ -63,6 +63,7 @@ import { SettingsPanel } from './SettingsPanel';
63
63
  import { NumberPicker } from './NumberPicker';
64
64
  import { ColorPicker } from './ColorPicker';
65
65
  import { LayersPanel } from './LayersPanel';
66
+ import { applyPenDraft, cancelPenDraft, polylineDraftFromNv, syncPolylineDraftToNv, } from './penDraftUtils';
66
67
  import { CloudMrNiivuePanel } from './CloudMrNiivuePanel';
67
68
  import { Niivue } from './NiivuePatcher';
68
69
  import NVSwitch from './Switch';
@@ -108,6 +109,7 @@ export var nv = new Niivue({
108
109
  penType: NI_PEN_TYPE.PEN
109
110
  });
110
111
  nv.opts.penBounds = 0;
112
+ nv.opts.penSize = 1;
111
113
  window.nv = nv;
112
114
  // The NiiVue component wraps all other components in the UI.
113
115
  // It is exported so that it can be used in other projects easily
@@ -171,15 +173,22 @@ export default function CloudMrNiivueViewer(props) {
171
173
  var _23 = useState(null), shapeDraft = _23[0], setShapeDraft = _23[1];
172
174
  var _24 = useState("freehand"), penDrawMode = _24[0], setPenDrawMode = _24[1];
173
175
  var _25 = useState(0), polylineVertexCount = _25[0], setPolylineVertexCount = _25[1];
176
+ var _26 = useState(null), penDraft = _26[0], setPenDraft = _26[1];
177
+ var _27 = useState(1), brushSize = _27[0], setBrushSize = _27[1];
174
178
  var shapeDraftRef = React.useRef(null);
179
+ var penDraftRef = React.useRef(null);
175
180
  var drawShapeToolRef = React.useRef(null);
181
+ var penDrawModeRef = React.useRef("freehand");
176
182
  drawShapeToolRef.current = drawShapeTool;
177
183
  shapeDraftRef.current = shapeDraft;
184
+ penDraftRef.current = penDraft;
185
+ penDrawModeRef.current = penDrawMode;
178
186
  React.useEffect(function () {
179
187
  nv.opts.penBounds = 0;
188
+ nv.opts.penSize = 1;
180
189
  }, []);
181
- var _26 = React.useState(1.0), gamma = _26[0], setGamma = _26[1];
182
- var _27 = React.useState(0), gammaKey = _27[0], setGammaKey = _27[1];
190
+ var _28 = React.useState(1.0), gamma = _28[0], setGamma = _28[1];
191
+ var _29 = React.useState(0), gammaKey = _29[0], setGammaKey = _29[1];
183
192
  // Niivue → React bridge so other places (Toolbar) can force the UI to reset
184
193
  nv.onResetGamma = function () {
185
194
  setGamma(1.0);
@@ -224,13 +233,13 @@ export default function CloudMrNiivueViewer(props) {
224
233
  // setLayers([...nv.volumes])
225
234
  // }, [])
226
235
  // values dualslider
227
- var _28 = useState(0), rangeKey = _28[0], setRangeKey = _28[1];
236
+ var _30 = useState(0), rangeKey = _30[0], setRangeKey = _30[1];
228
237
  nv.onResetContrast = function () {
229
238
  setRangeKey(rangeKey + 1);
230
239
  };
231
- var _29 = useState([0, 0, 0]), boundMins = _29[0], setBoundMins = _29[1];
232
- var _30 = useState([1, 1, 1]), boundMaxs = _30[0], setBoundMaxs = _30[1];
233
- var _31 = useState([0.5, 0.5, 0.5]), mms = _31[0], setMMs = _31[1];
240
+ var _31 = useState([0, 0, 0]), boundMins = _31[0], setBoundMins = _31[1];
241
+ var _32 = useState([1, 1, 1]), boundMaxs = _32[0], setBoundMaxs = _32[1];
242
+ var _33 = useState([0.5, 0.5, 0.5]), mms = _33[0], setMMs = _33[1];
234
243
  nv.onImageLoaded = function () {
235
244
  var _a, _b;
236
245
  var oldCrosshairPos = __spreadArray([], nv.scene.crosshairPos, true);
@@ -513,7 +522,7 @@ export default function CloudMrNiivueViewer(props) {
513
522
  setTextsVisible(true);
514
523
  }
515
524
  }
516
- var _32 = useState("pan"), dragMode = _32[0], setDragMode = _32[1];
525
+ var _34 = useState("pan"), dragMode = _34[0], setDragMode = _34[1];
517
526
  function nvSetDragMode(dragMode) {
518
527
  switch (dragMode) {
519
528
  case "none":
@@ -550,11 +559,15 @@ export default function CloudMrNiivueViewer(props) {
550
559
  nv.setDrawingEnabled(enabled);
551
560
  nv.drawScene();
552
561
  }
562
+ function nvUpdateBrushSize(size) {
563
+ setBrushSize(size);
564
+ nv.opts.penBounds = (size - 1) / 2;
565
+ nv.opts.penSize = size;
566
+ }
553
567
  function nvUpdateDrawPen(a) {
554
568
  var raw = Number(a.target.value);
555
569
  setDrawPen(raw);
556
570
  var penValue = raw;
557
- nv.opts.penBounds = 0;
558
571
  nv.setPenValue(penValue & 7, penValue > 0);
559
572
  if (penValue == 8) {
560
573
  nv.setPenValue(0, true);
@@ -722,7 +735,7 @@ export default function CloudMrNiivueViewer(props) {
722
735
  nv.opts.crosshairWidth = w;
723
736
  nv.drawScene();
724
737
  }
725
- var _33 = useState({}), labelMapping = _33[0], setLabelMapping = _33[1];
738
+ var _35 = useState({}), labelMapping = _35[0], setLabelMapping = _35[1];
726
739
  function resampleImage(mapping) {
727
740
  if (mapping === void 0) { mapping = labelMapping; }
728
741
  var el = typeof document !== "undefined" ? document.getElementById("histoplot") : null;
@@ -738,30 +751,82 @@ export default function CloudMrNiivueViewer(props) {
738
751
  function syncPenDrawMode(mode) {
739
752
  var _a;
740
753
  setPenDrawMode(mode);
754
+ penDrawModeRef.current = mode;
741
755
  nv.opts.polylinePenMode = mode === "polyline";
742
756
  nv.opts.isFilledPen = mode === "freehand";
757
+ nv.opts.deferFreehandCommit =
758
+ drawShapeToolRef.current === "pen" && mode === "freehand";
743
759
  if (mode === "freehand") {
744
760
  (_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
745
761
  }
746
762
  }
747
- function cancelPolylineDraft() {
763
+ function cancelPenDraftHandler() {
748
764
  var _a;
749
- (_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
765
+ var draft = penDraftRef.current;
766
+ if (!draft)
767
+ return;
768
+ cancelPenDraft(nv, draft);
769
+ if (draft.kind === "polyline") {
770
+ (_a = nv.cloudMrResetPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
771
+ setPolylineVertexCount(0);
772
+ }
773
+ setPenDraft(null);
774
+ penDraftRef.current = null;
775
+ nv._cloudMrPenDraftActive = false;
776
+ if (drawShapeToolRef.current === "pen") {
777
+ nvSetDrawingEnabled(true);
778
+ }
750
779
  }
751
- function finishPolylineOpen() {
752
- if (nv.cloudMrFinishPolyline(false)) {
753
- setDrawingChanged(true);
754
- resampleImage();
780
+ function applyPenDraftHandler(fillClosed) {
781
+ var _a;
782
+ if (fillClosed === void 0) { fillClosed = false; }
783
+ var draft = penDraftRef.current;
784
+ if (!draft)
785
+ return;
786
+ applyPenDraft(nv, draft, { fillClosed: fillClosed });
787
+ if (draft.kind === "polyline") {
788
+ (_a = nv.cloudMrResetPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
789
+ setPolylineVertexCount(0);
755
790
  }
791
+ setPenDraft(null);
792
+ penDraftRef.current = null;
793
+ nv._cloudMrPenDraftActive = false;
794
+ setDrawingChanged(true);
795
+ if (drawShapeToolRef.current === "pen") {
796
+ nvSetDrawingEnabled(true);
797
+ }
798
+ resampleImage();
756
799
  }
757
- function finishPolylineClosed() {
758
- if (nv.cloudMrFinishPolyline(true)) {
759
- setDrawingChanged(true);
760
- resampleImage();
800
+ function onPenDraftChange(draft) {
801
+ setPenDraft(draft);
802
+ penDraftRef.current = draft;
803
+ if (draft.kind === "polyline") {
804
+ syncPolylineDraftToNv(nv, draft);
761
805
  }
762
806
  }
807
+ function cancelPolylineDraft() {
808
+ cancelPenDraftHandler();
809
+ }
810
+ nv.onPenDraftReady = function (draft) {
811
+ setPenDraft(draft);
812
+ penDraftRef.current = draft;
813
+ nv._cloudMrPenDraftActive = true;
814
+ nvSetDrawingEnabled(false);
815
+ };
763
816
  nv.onPolylineChange = function (count) {
817
+ var _a;
764
818
  setPolylineVertexCount(count);
819
+ if (penDrawModeRef.current === "polyline" && count >= 2) {
820
+ var draft = polylineDraftFromNv(nv);
821
+ if (draft) {
822
+ setPenDraft(draft);
823
+ penDraftRef.current = draft;
824
+ }
825
+ }
826
+ else if (((_a = penDraftRef.current) === null || _a === void 0 ? void 0 : _a.kind) === "polyline") {
827
+ setPenDraft(null);
828
+ penDraftRef.current = null;
829
+ }
765
830
  };
766
831
  function cancelShapeDraft() {
767
832
  var draft = shapeDraftRef.current;
@@ -801,7 +866,7 @@ export default function CloudMrNiivueViewer(props) {
801
866
  nvSetDrawingEnabled(false);
802
867
  };
803
868
  React.useEffect(function () {
804
- if (!shapeDraft && !(penDrawMode === "polyline" && polylineVertexCount >= 2)) {
869
+ if (!shapeDraft && !penDraft) {
805
870
  return undefined;
806
871
  }
807
872
  var onKeyDown = function (event) {
@@ -810,8 +875,8 @@ export default function CloudMrNiivueViewer(props) {
810
875
  if (shapeDraft) {
811
876
  cancelShapeDraft();
812
877
  }
813
- else if (polylineVertexCount >= 2) {
814
- cancelPolylineDraft();
878
+ else if (penDraft) {
879
+ cancelPenDraftHandler();
815
880
  }
816
881
  return;
817
882
  }
@@ -820,19 +885,19 @@ export default function CloudMrNiivueViewer(props) {
820
885
  if (shapeDraft) {
821
886
  applyShapeDraft();
822
887
  }
823
- else if (penDrawMode === "polyline" && polylineVertexCount >= 2) {
824
- finishPolylineOpen();
888
+ else if (penDraft) {
889
+ applyPenDraftHandler(false);
825
890
  }
826
891
  }
827
892
  };
828
893
  window.addEventListener("keydown", onKeyDown);
829
894
  return function () { return window.removeEventListener("keydown", onKeyDown); };
830
- }, [shapeDraft, penDrawMode, polylineVertexCount]);
895
+ }, [shapeDraft, penDraft]);
831
896
  function nvUpdateSelectionBoxColor(rgb01) {
832
897
  setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
833
898
  nv.setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
834
899
  }
835
- var _34 = React.useState('axial'), sliceType = _34[0], setSliceType = _34[1];
900
+ var _36 = React.useState('axial'), sliceType = _36[0], setSliceType = _36[1];
836
901
  function nvUpdateSliceType(newSliceType) {
837
902
  setSliceType(newSliceType);
838
903
  if (newSliceType === 'axial') {
@@ -972,14 +1037,14 @@ export default function CloudMrNiivueViewer(props) {
972
1037
  return [2 /*return*/];
973
1038
  });
974
1039
  }); };
975
- var _35 = useState(''), selectedROI = _35[0], setSelectedDrawingLayer = _35[1];
976
- var _36 = useState(false), saveDialogOpen = _36[0], setSaveDialogOpen = _36[1];
977
- var _37 = useState(function () {
978
- }), saveConfirmCallback = _37[0], setSaveConfirmCallback = _37[1];
979
- var _38 = useState(false), confirmationOpen = _38[0], setConfirmationOpen = _38[1];
980
- var _39 = useState(function () { }), warningConfirmationCallback = _39[0], setWarningConfirmationCallback = _39[1];
981
- var _40 = useState(function () { }), warningCancelCallback = _40[0], setWarningCancelCallback = _40[1];
982
- var _41 = useState(false), drawingChanged = _41[0], setDrawingChanged = _41[1];
1040
+ var _37 = useState(''), selectedROI = _37[0], setSelectedDrawingLayer = _37[1];
1041
+ var _38 = useState(false), saveDialogOpen = _38[0], setSaveDialogOpen = _38[1];
1042
+ var _39 = useState(function () {
1043
+ }), saveConfirmCallback = _39[0], setSaveConfirmCallback = _39[1];
1044
+ var _40 = useState(false), confirmationOpen = _40[0], setConfirmationOpen = _40[1];
1045
+ var _41 = useState(function () { }), warningConfirmationCallback = _41[0], setWarningConfirmationCallback = _41[1];
1046
+ var _42 = useState(function () { }), warningCancelCallback = _42[0], setWarningCancelCallback = _42[1];
1047
+ var _43 = useState(false), drawingChanged = _43[0], setDrawingChanged = _43[1];
983
1048
  // When new drawing strokes are made on top of a saved ROI, reset the dropdown
984
1049
  // so it shows the "ROI Layer" placeholder instead of the stale saved name.
985
1050
  React.useEffect(function () {
@@ -1306,8 +1371,8 @@ export default function CloudMrNiivueViewer(props) {
1306
1371
  cancelShapeDraft();
1307
1372
  return;
1308
1373
  }
1309
- if (polylineVertexCount > 0) {
1310
- cancelPolylineDraft();
1374
+ if (penDraftRef.current) {
1375
+ cancelPenDraftHandler();
1311
1376
  return;
1312
1377
  }
1313
1378
  nv.drawUndo();
@@ -1326,8 +1391,12 @@ export default function CloudMrNiivueViewer(props) {
1326
1391
  onPenDrawModeChange: syncPenDrawMode,
1327
1392
  polylineVertexCount: polylineVertexCount,
1328
1393
  onCancelPolyline: cancelPolylineDraft,
1329
- onFinishPolyline: finishPolylineOpen,
1330
- onCloseFillPolyline: finishPolylineClosed
1394
+ onApplyPenDraft: function () { return applyPenDraftHandler(false); },
1395
+ onCancelPenDraft: cancelPenDraftHandler,
1396
+ onCloseFillPenDraft: function () { return applyPenDraftHandler(true); },
1397
+ penDraftActive: penDraft != null,
1398
+ brushSize: brushSize,
1399
+ updateBrushSize: nvUpdateBrushSize
1331
1400
  };
1332
1401
  return (_jsxs(Box, __assign({ sx: {
1333
1402
  display: 'flex',
@@ -1343,7 +1412,7 @@ export default function CloudMrNiivueViewer(props) {
1343
1412
  props.rois[selectedROI].filename : undefined) }), props.niis[selectedVolume] != undefined && _jsx(CloudMrNiivuePanel, { nv: nv, transformFactors: transformFactors, decimalPrecision: decimalPrecision, locationData: locationData, locationTableVisible: locationTableVisible, pipelineID: props.pipelineID, resampleImage: resampleImage, rois: rois, drawToolkitProps: drawToolkitProps, drawShapeTool: drawShapeTool, setDrawShapeTool: setDrawShapeTool, mins: boundMins, maxs: boundMaxs, mms: mms, min: min, max: max, setMin: setMin, setMax: setMax, unzipAndRenderROI: unpackROI, zipAndSendROI: zipAndSendDrawingLayer, setLabelAlias: setLabelAlias, onAfterRoiUpload: function () {
1344
1413
  var _a;
1345
1414
  void ((_a = props.refreshPipelineRois) === null || _a === void 0 ? void 0 : _a.call(props));
1346
- }, gamma: gamma, gammaKey: gammaKey, setGamma: setGamma, shapeDraft: shapeDraft, onShapeDraftChange: onShapeDraftChange, onApplyShapeDraft: applyShapeDraft, onCancelShapeDraft: cancelShapeDraft }, "".concat(selectedVolume))] })));
1415
+ }, gamma: gamma, gammaKey: gammaKey, setGamma: setGamma, shapeDraft: shapeDraft, onShapeDraftChange: onShapeDraftChange, onApplyShapeDraft: applyShapeDraft, onCancelShapeDraft: cancelShapeDraft, penDraft: penDraft, onPenDraftChange: onPenDraftChange, onApplyPenDraft: function () { return applyPenDraftHandler(false); }, onCancelPenDraft: cancelPenDraftHandler }, "".concat(selectedVolume))] })));
1347
1416
  }
1348
1417
  function niiToVolume(nii) {
1349
1418
  return {
@@ -9,6 +9,7 @@ import {
9
9
  } from "./shapeDraftUtils.js";
10
10
  import {
11
11
  addPolylineVertex,
12
+ axCorSagFromMouse,
12
13
  cancelPolyline,
13
14
  finishPolyline,
14
15
  isClickWithoutDrag,
@@ -16,6 +17,10 @@ import {
16
17
  previewPolylineSegment,
17
18
  resetPolylineState,
18
19
  } from "./polylinePenUtils.js";
20
+ import {
21
+ captureFreehandDraft,
22
+ shouldDeferFreehandCommit,
23
+ } from "./penDraftUtils.js";
19
24
 
20
25
  /*
21
26
  * bitmapOverlay — in-browser state only: remembers each voxel's label *before* Group
@@ -496,6 +501,31 @@ Niivue.prototype.drawPenFilled = function () {
496
501
  this.drawBitmap[index] = value;
497
502
  }
498
503
  })
504
+ if (shouldDeferFreehandCommit(this) && this._cloudMrFreehandSessionStartBitmap) {
505
+ this._cloudMrSkipNextUndoBitmap = true;
506
+ this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
507
+ for (let i = 0; i < this.drawBitmap.length; i++) {
508
+ let pen = this.drawBitmap[i];
509
+ if (!this.getLabelVisibility(pen)) {
510
+ this.hiddenBitmap[i] = this.drawBitmap[i];
511
+ this.drawBitmap[i] = 0;
512
+ }
513
+ }
514
+ this.refreshDrawing(false);
515
+ this.drawScene();
516
+ const axCorSag =
517
+ this.drawPenAxCorSag >= 0 ? this.drawPenAxCorSag : this._cloudMrFreehandAxCorSag;
518
+ const draft = captureFreehandDraft(
519
+ this,
520
+ this._cloudMrFreehandSessionStartBitmap,
521
+ axCorSag,
522
+ );
523
+ this._cloudMrFreehandSessionStartBitmap = null;
524
+ if (draft && typeof this.onPenDraftReady === "function") {
525
+ this.onPenDraftReady(draft);
526
+ }
527
+ return;
528
+ }
499
529
  this.drawAddUndoBitmap()
500
530
  // Post-processing to hide hidden voxels
501
531
  this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
@@ -1435,6 +1465,21 @@ Niivue.prototype.cloudMrResetPolyline = function cloudMrResetPolyline() {
1435
1465
  resetPolylineState(this);
1436
1466
  };
1437
1467
 
1468
+ const _mouseDownListener = Niivue.prototype.mouseDownListener;
1469
+ Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
1470
+ if (shouldDeferFreehandCommit(this) && this.drawBitmap) {
1471
+ this._cloudMrFreehandSessionStartBitmap = this.drawBitmap.slice();
1472
+ this._cloudMrFreehandAxCorSag = -1;
1473
+ }
1474
+ _mouseDownListener.call(this, e);
1475
+ if (shouldDeferFreehandCommit(this) && this._cloudMrFreehandSessionStartBitmap) {
1476
+ const axCorSag = axCorSagFromMouse(this);
1477
+ if (axCorSag >= 0) {
1478
+ this._cloudMrFreehandAxCorSag = axCorSag;
1479
+ }
1480
+ }
1481
+ };
1482
+
1438
1483
  const _mouseClick = Niivue.prototype.mouseClick;
1439
1484
  Niivue.prototype.mouseClick = function cloudMrMouseClick(...args) {
1440
1485
  if (isPolylinePenActive(this)) {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Adjust handles for polyline (vertex drag) or freehand (bbox move/resize) drafts.
3
+ */
4
+ export function PenDraftOverlay({ nv, draft, onDraftChange, overlayKey }: {
5
+ nv: any;
6
+ draft: any;
7
+ onDraftChange: any;
8
+ overlayKey: any;
9
+ }): import("react/jsx-runtime").JSX.Element | null;
10
+ export default PenDraftOverlay;
@@ -0,0 +1,201 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
13
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
14
+ if (ar || !(i in from)) {
15
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
16
+ ar[i] = from[i];
17
+ }
18
+ }
19
+ return to.concat(ar || Array.prototype.slice.call(from));
20
+ };
21
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
23
+ import { boundsToFreehandCorners, redrawFreehandDraft, redrawPolylineDraft, resizeFreehandDraft, syncPolylineDraftToNv, translateFreehandDraft, translatePolylineVertices, updatePolylineVertex, } from "./penDraftUtils";
24
+ import { canvasDeltaToVoxDelta, clientToCanvasPos, translatePt, voxToOverlayPos, voxUnderClient, } from "./shapeDraftUtils";
25
+ var HANDLE_SIZE = 10;
26
+ var ACCENT = "#580f8b";
27
+ /**
28
+ * Adjust handles for polyline (vertex drag) or freehand (bbox move/resize) drafts.
29
+ */
30
+ export function PenDraftOverlay(_a) {
31
+ var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, overlayKey = _a.overlayKey;
32
+ var dragRef = useRef(null);
33
+ var draftRef = useRef(draft);
34
+ draftRef.current = draft;
35
+ var onPointerMoveRef = useRef(null);
36
+ var finishDragRef = useRef(null);
37
+ var _b = useState(0), setTick = _b[1];
38
+ var bump = useCallback(function () { return setTick(function (t) { return t + 1; }); }, []);
39
+ useEffect(function () {
40
+ var onMove = function () { return bump(); };
41
+ window.addEventListener("resize", onMove);
42
+ return function () { return window.removeEventListener("resize", onMove); };
43
+ }, [bump]);
44
+ var isPolyline = (draft === null || draft === void 0 ? void 0 : draft.kind) === "polyline";
45
+ var vertexCss = useMemo(function () {
46
+ if (!isPolyline || !(draft === null || draft === void 0 ? void 0 : draft.vertices))
47
+ return [];
48
+ return draft.vertices
49
+ .map(function (vox) { return voxToOverlayPos(nv, vox, draft.axCorSag); })
50
+ .filter(Boolean);
51
+ }, [draft, isPolyline, nv, overlayKey]);
52
+ var freehandCorners = useMemo(function () {
53
+ if (isPolyline || !(draft === null || draft === void 0 ? void 0 : draft.bounds))
54
+ return [];
55
+ return boundsToFreehandCorners(draft.bounds, draft.axCorSag);
56
+ }, [draft, isPolyline, overlayKey]);
57
+ var cornerCss = useMemo(function () {
58
+ if (isPolyline)
59
+ return vertexCss;
60
+ return freehandCorners
61
+ .map(function (vox) { return voxToOverlayPos(nv, vox, draft.axCorSag); })
62
+ .filter(Boolean);
63
+ }, [draft === null || draft === void 0 ? void 0 : draft.axCorSag, freehandCorners, isPolyline, nv, overlayKey, vertexCss]);
64
+ var centerCss = useMemo(function () {
65
+ var _a;
66
+ if (!draft)
67
+ return null;
68
+ if (isPolyline && ((_a = draft.vertices) === null || _a === void 0 ? void 0 : _a.length)) {
69
+ var xs = draft.vertices.map(function (v) { return v[0]; });
70
+ var ys = draft.vertices.map(function (v) { return v[1]; });
71
+ var zs = draft.vertices.map(function (v) { return v[2]; });
72
+ return voxToOverlayPos(nv, [
73
+ (Math.min.apply(Math, xs) + Math.max.apply(Math, xs)) / 2,
74
+ (Math.min.apply(Math, ys) + Math.max.apply(Math, ys)) / 2,
75
+ (Math.min.apply(Math, zs) + Math.max.apply(Math, zs)) / 2,
76
+ ], draft.axCorSag);
77
+ }
78
+ if (draft.bounds) {
79
+ var b = draft.bounds;
80
+ return voxToOverlayPos(nv, [(b.x1 + b.x2) / 2, (b.y1 + b.y2) / 2, (b.z1 + b.z2) / 2], draft.axCorSag);
81
+ }
82
+ return null;
83
+ }, [draft, isPolyline, nv, overlayKey]);
84
+ var boxStyle = useMemo(function () {
85
+ if (cornerCss.length < 2)
86
+ return null;
87
+ var xs = cornerCss.map(function (p) { return p.x; });
88
+ var ys = cornerCss.map(function (p) { return p.y; });
89
+ var left = Math.min.apply(Math, xs);
90
+ var top = Math.min.apply(Math, ys);
91
+ var width = Math.max.apply(Math, xs) - left;
92
+ var height = Math.max.apply(Math, ys) - top;
93
+ if (width < 2 && height < 2)
94
+ return null;
95
+ return { left: left, top: top, width: Math.max(width, 2), height: Math.max(height, 2) };
96
+ }, [cornerCss]);
97
+ var applyDraft = useCallback(function (nextDraft) {
98
+ if (nextDraft.kind === "polyline") {
99
+ redrawPolylineDraft(nv, nextDraft);
100
+ syncPolylineDraftToNv(nv, nextDraft);
101
+ }
102
+ else {
103
+ redrawFreehandDraft(nv, nextDraft);
104
+ }
105
+ onDraftChange(nextDraft);
106
+ }, [nv, onDraftChange]);
107
+ finishDragRef.current = function () {
108
+ dragRef.current = null;
109
+ if (onPointerMoveRef.current) {
110
+ window.removeEventListener("pointermove", onPointerMoveRef.current);
111
+ }
112
+ if (finishDragRef.current) {
113
+ window.removeEventListener("pointerup", finishDragRef.current);
114
+ }
115
+ };
116
+ onPointerMoveRef.current = function (event) {
117
+ var drag = dragRef.current;
118
+ var current = draftRef.current;
119
+ if (!drag || !current)
120
+ return;
121
+ event.preventDefault();
122
+ if (drag.mode === "move") {
123
+ var canvas = nv.canvas || document.getElementById("niiCanvas");
124
+ if (!canvas)
125
+ return;
126
+ var startCanvas = clientToCanvasPos(canvas, drag.startClientX, drag.startClientY);
127
+ var endCanvas = clientToCanvasPos(canvas, event.clientX, event.clientY);
128
+ var delta = canvasDeltaToVoxDelta(nv, startCanvas, endCanvas);
129
+ if (current.kind === "polyline") {
130
+ applyDraft(__assign(__assign({}, current), { vertices: translatePolylineVertices(drag.startVertices, delta) }));
131
+ }
132
+ else {
133
+ applyDraft(translateFreehandDraft(current, delta));
134
+ }
135
+ return;
136
+ }
137
+ var vox = voxUnderClient(nv, event.clientX, event.clientY);
138
+ if (!vox)
139
+ return;
140
+ if (current.kind === "polyline") {
141
+ applyDraft(__assign(__assign({}, current), { vertices: updatePolylineVertex(drag.startVertices, drag.cornerIndex, vox) }));
142
+ }
143
+ else {
144
+ applyDraft(resizeFreehandDraft(current, drag.cornerIndex, vox));
145
+ }
146
+ };
147
+ var startDrag = useCallback(function (event, mode, cornerIndex) {
148
+ var _a, _b;
149
+ if (cornerIndex === void 0) { cornerIndex = -1; }
150
+ event.preventDefault();
151
+ event.stopPropagation();
152
+ dragRef.current = {
153
+ mode: mode,
154
+ cornerIndex: cornerIndex,
155
+ startClientX: event.clientX,
156
+ startClientY: event.clientY,
157
+ startVertices: (_b = (_a = draft.vertices) === null || _a === void 0 ? void 0 : _a.map(function (v) { return __spreadArray([], v, true); })) !== null && _b !== void 0 ? _b : []
158
+ };
159
+ window.addEventListener("pointermove", onPointerMoveRef.current);
160
+ window.addEventListener("pointerup", finishDragRef.current);
161
+ }, [draft.vertices]);
162
+ useEffect(function () { return function () {
163
+ var _a;
164
+ (_a = finishDragRef.current) === null || _a === void 0 ? void 0 : _a.call(finishDragRef);
165
+ }; }, []);
166
+ if (!draft || !boxStyle)
167
+ return null;
168
+ var handleStyle = {
169
+ position: "absolute",
170
+ width: HANDLE_SIZE,
171
+ height: HANDLE_SIZE,
172
+ marginLeft: -HANDLE_SIZE / 2,
173
+ marginTop: -HANDLE_SIZE / 2,
174
+ borderRadius: "50%",
175
+ background: "#fff",
176
+ border: "2px solid ".concat(ACCENT),
177
+ boxSizing: "border-box",
178
+ cursor: "pointer",
179
+ zIndex: 3,
180
+ touchAction: "none",
181
+ pointerEvents: "auto"
182
+ };
183
+ return (_jsxs("div", __assign({ style: {
184
+ position: "absolute",
185
+ inset: 0,
186
+ zIndex: 2,
187
+ pointerEvents: "none",
188
+ overflow: "hidden"
189
+ }, "aria-label": "Adjust pen ROI" }, { children: [_jsx("div", { style: {
190
+ position: "absolute",
191
+ left: boxStyle.left,
192
+ top: boxStyle.top,
193
+ width: boxStyle.width,
194
+ height: boxStyle.height,
195
+ border: "2px dashed ".concat(ACCENT),
196
+ background: "rgba(88, 15, 139, 0.08)",
197
+ boxSizing: "border-box",
198
+ pointerEvents: "none"
199
+ } }), centerCss && (_jsx("div", { role: "presentation", onPointerDown: function (e) { return startDrag(e, "move"); }, style: __assign(__assign({}, handleStyle), { left: centerCss.x, top: centerCss.y, cursor: "move", borderRadius: 2, width: 12, height: 12, marginLeft: -6, marginTop: -6 }), title: "Move shape" })), cornerCss.map(function (pos, i) { return (_jsx("div", { role: "presentation", onPointerDown: function (e) { return startDrag(e, "corner", i); }, style: __assign(__assign({}, handleStyle), { left: pos.x, top: pos.y, cursor: isPolyline ? "grab" : "nwse-resize" }), title: isPolyline ? "Move vertex" : "Resize shape" }, "handle-".concat(i))); })] })));
200
+ }
201
+ export default PenDraftOverlay;
@@ -0,0 +1,5 @@
1
+ export function BrushSizeSlider({ label, brushSize, updateBrushSize }: {
2
+ label: any;
3
+ brushSize: any;
4
+ updateBrushSize: any;
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,36 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
13
+ import { Stack, Slider, Typography } from "@mui/material";
14
+ var SLIDER_SX = {
15
+ width: "80%",
16
+ color: "#fff",
17
+ "& .MuiSlider-track": { backgroundColor: "#fff", border: "none" },
18
+ "& .MuiSlider-rail": { backgroundColor: "rgba(255,255,255,0.3)" },
19
+ "& .MuiSlider-thumb": {
20
+ backgroundColor: "#fff",
21
+ border: "2px solid #fff",
22
+ "&:hover, &.Mui-focusVisible, &.Mui-active": {
23
+ boxShadow: "0 0 0 8px rgba(255,255,255,0.16)"
24
+ }
25
+ },
26
+ "& .MuiSlider-mark": { backgroundColor: "#fff" },
27
+ "& .MuiSlider-markActive": { backgroundColor: "#fff" },
28
+ "& .MuiSlider-valueLabel": {
29
+ backgroundColor: "#fff",
30
+ color: "#000"
31
+ }
32
+ };
33
+ export function BrushSizeSlider(_a) {
34
+ var label = _a.label, brushSize = _a.brushSize, updateBrushSize = _a.updateBrushSize;
35
+ return (_jsxs(Stack, __assign({ sx: { mb: 0.5, px: 0.5 }, alignItems: "center", width: "100%" }, { children: [_jsxs(Typography, __assign({ color: "white", noWrap: true, width: "100%", marginLeft: "6pt", fontSize: "11pt", sx: { userSelect: "none" } }, { children: [label, ": ", brushSize] })), _jsx(Slider, { value: brushSize, sx: SLIDER_SX, step: 2, min: 1, max: 15, marks: true, onChange: function (_event, value) { return updateBrushSize(value); } })] })));
36
+ }
@@ -1,4 +1,4 @@
1
- export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, onCancelPolyline, onFinishPolyline, onCloseFillPolyline, shapeDraftActive, onApplyShapeDraft, onCancelShapeDraft, }: {
1
+ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive, onApplyPenDraft, onCancelPenDraft, onCloseFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onApplyShapeDraft, onCancelShapeDraft, }: {
2
2
  expanded: any;
3
3
  updateDrawPen: any;
4
4
  setDrawingEnabled: any;
@@ -6,9 +6,12 @@ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEna
6
6
  penDrawMode?: string | undefined;
7
7
  onPenDrawModeChange: any;
8
8
  polylineVertexCount?: number | undefined;
9
- onCancelPolyline: any;
10
- onFinishPolyline: any;
11
- onCloseFillPolyline: any;
9
+ penDraftActive?: boolean | undefined;
10
+ onApplyPenDraft: any;
11
+ onCancelPenDraft: any;
12
+ onCloseFillPenDraft: any;
13
+ brushSize?: number | undefined;
14
+ updateBrushSize: any;
12
15
  shapeDraftActive?: boolean | undefined;
13
16
  onApplyShapeDraft: any;
14
17
  onCancelShapeDraft: any;
@@ -11,13 +11,13 @@ var __assign = (this && this.__assign) || function () {
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  /**
14
- * Shared ROI label color row (NiiVue pen indices 1–6). Used by pen, rectangle, and ellipse tools.
15
- * Pen palette adds freehand vs straight-line mode; rectangle/ellipse show Apply/Cancel while adjusting.
14
+ * Pen palette adds freehand vs polyline mode; pen/shape drafts show Apply/Cancel while adjusting.
16
15
  */
17
16
  import { Stack, IconButton, Button, Tooltip, Typography } from "@mui/material";
18
17
  import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
19
18
  import CheckIcon from "@mui/icons-material/Check";
20
19
  import CloseIcon from "@mui/icons-material/Close";
20
+ import { BrushSizeSlider } from "./BrushSizeSlider";
21
21
  var FILLED_COLORS = [
22
22
  { sx: { color: "red" } },
23
23
  { sx: { color: "green" } },
@@ -37,7 +37,7 @@ var modeBtnSx = function (active) { return ({
37
37
  px: 0.75
38
38
  }); };
39
39
  export default function DrawColorPlatte(_a) {
40
- var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.showPenModes, showPenModes = _b === void 0 ? false : _b, _c = _a.penDrawMode, penDrawMode = _c === void 0 ? "freehand" : _c, onPenDrawModeChange = _a.onPenDrawModeChange, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, onCancelPolyline = _a.onCancelPolyline, onFinishPolyline = _a.onFinishPolyline, onCloseFillPolyline = _a.onCloseFillPolyline, _e = _a.shapeDraftActive, shapeDraftActive = _e === void 0 ? false : _e, onApplyShapeDraft = _a.onApplyShapeDraft, onCancelShapeDraft = _a.onCancelShapeDraft;
40
+ var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.showPenModes, showPenModes = _b === void 0 ? false : _b, _c = _a.penDrawMode, penDrawMode = _c === void 0 ? "freehand" : _c, onPenDrawModeChange = _a.onPenDrawModeChange, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, _e = _a.penDraftActive, penDraftActive = _e === void 0 ? false : _e, onApplyPenDraft = _a.onApplyPenDraft, onCancelPenDraft = _a.onCancelPenDraft, onCloseFillPenDraft = _a.onCloseFillPenDraft, _f = _a.brushSize, brushSize = _f === void 0 ? 1 : _f, updateBrushSize = _a.updateBrushSize, _g = _a.shapeDraftActive, shapeDraftActive = _g === void 0 ? false : _g, onApplyShapeDraft = _a.onApplyShapeDraft, onCancelShapeDraft = _a.onCancelShapeDraft;
41
41
  return (_jsxs(Stack, __assign({ style: {
42
42
  position: "absolute",
43
43
  top: "100%",
@@ -51,10 +51,10 @@ export default function DrawColorPlatte(_a) {
51
51
  borderTopLeftRadius: "6pt",
52
52
  borderTopRightRadius: "6pt",
53
53
  background: "#333"
54
- }, direction: "column", spacing: 0.5, sx: { py: expanded ? 0.5 : 0 } }, { children: [showPenModes && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 0.5, sx: { px: 0.75, pt: 0.25 } }, { children: [_jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("freehand"); }, sx: modeBtnSx(penDrawMode === "freehand") }, { children: "Freehand" })), _jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("polyline"); }, sx: modeBtnSx(penDrawMode === "polyline") }, { children: "Straight lines" }))] }))), _jsx(Stack, __assign({ direction: "row" }, { children: FILLED_COLORS.map(function (color, index) { return (_jsx(IconButton, __assign({ onClick: function () {
54
+ }, direction: "column", spacing: 0.5, sx: { py: expanded ? 0.5 : 0 } }, { children: [showPenModes && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 0.5, sx: { px: 0.75, pt: 0.25 } }, { children: [_jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("freehand"); }, sx: modeBtnSx(penDrawMode === "freehand") }, { children: "Freehand" })), _jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("polyline"); }, sx: modeBtnSx(penDrawMode === "polyline") }, { children: "Polyline" }))] }))), showPenModes && expanded && updateBrushSize && (_jsx(BrushSizeSlider, { label: "Line thickness", brushSize: brushSize, updateBrushSize: updateBrushSize })), _jsx(Stack, __assign({ direction: "row" }, { children: FILLED_COLORS.map(function (color, index) { return (_jsx(IconButton, __assign({ onClick: function () {
55
55
  updateDrawPen({ target: { value: index + 1 } });
56
56
  setDrawingEnabled(true);
57
- } }, { children: _jsx(FiberManualRecordIcon, { sx: color.sx }) }), index)); }) })), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount === 0 && (_jsx(Typography, __assign({ sx: { px: 1, pb: 0.5, fontSize: "0.68rem", color: "#aaa", userSelect: "none" } }, { children: "Click each corner to draw connected straight lines" }))), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount >= 2 && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "space-between", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: [_jsx(Tooltip, __assign({ title: "Discard current outline" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel polyline", onClick: function () { return onCancelPolyline === null || onCancelPolyline === void 0 ? void 0 : onCancelPolyline(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
57
+ } }, { children: _jsx(FiberManualRecordIcon, { sx: color.sx }) }), index)); }) })), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount === 0 && (_jsx(Typography, __assign({ sx: { px: 1, pb: 0.5, fontSize: "0.68rem", color: "#aaa", userSelect: "none" } }, { children: "Click each vertex to draw connected line segments" }))), showPenModes && penDraftActive && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "space-between", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: [_jsx(Tooltip, __assign({ title: "Cancel (Esc)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel pen draft", onClick: function () { return onCancelPenDraft === null || onCancelPenDraft === void 0 ? void 0 : onCancelPenDraft(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
58
58
  color: "#ccc",
59
59
  fontSize: ACTION_FONT_SIZE,
60
60
  textTransform: "none",
@@ -62,14 +62,14 @@ export default function DrawColorPlatte(_a) {
62
62
  py: 0.25,
63
63
  px: 0.75,
64
64
  "& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
65
- } }, { children: "Cancel" })) })), _jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 1 }, { children: [polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: "Connect last point to first and fill" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "close and fill polyline", onClick: function () { return onCloseFillPolyline === null || onCloseFillPolyline === void 0 ? void 0 : onCloseFillPolyline(); }, sx: {
65
+ } }, { children: "Cancel" })) })), _jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 1 }, { children: [penDrawMode === "polyline" && polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: "Connect last point to first and fill" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "close and fill polyline", onClick: function () { return onCloseFillPenDraft === null || onCloseFillPenDraft === void 0 ? void 0 : onCloseFillPenDraft(); }, sx: {
66
66
  color: "#c9a0e8",
67
67
  fontSize: ACTION_FONT_SIZE,
68
68
  textTransform: "none",
69
69
  minWidth: 0,
70
70
  py: 0.25,
71
71
  px: 0.75
72
- } }, { children: "Close & fill" })) }))), _jsx(Tooltip, __assign({ title: "Keep outline only (Enter)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "finish polyline", onClick: function () { return onFinishPolyline === null || onFinishPolyline === void 0 ? void 0 : onFinishPolyline(); }, startIcon: _jsx(CheckIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
72
+ } }, { children: "Close & fill" })) }))), _jsx(Tooltip, __assign({ title: "Apply (Enter)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "apply pen draft", onClick: function () { return onApplyPenDraft === null || onApplyPenDraft === void 0 ? void 0 : onApplyPenDraft(); }, startIcon: _jsx(CheckIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
73
73
  color: "#c9a0e8",
74
74
  fontSize: ACTION_FONT_SIZE,
75
75
  textTransform: "none",
@@ -77,7 +77,7 @@ export default function DrawColorPlatte(_a) {
77
77
  py: 0.25,
78
78
  px: 0.75,
79
79
  "& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
80
- } }, { children: "Done" })) }))] }))] }))), shapeDraftActive && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "space-between", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: [_jsx(Tooltip, __assign({ title: "Cancel shape (Esc)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel shape", onClick: function () { return onCancelShapeDraft === null || onCancelShapeDraft === void 0 ? void 0 : onCancelShapeDraft(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
80
+ } }, { children: "Apply" })) }))] }))] }))), shapeDraftActive && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "space-between", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: [_jsx(Tooltip, __assign({ title: "Cancel shape (Esc)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel shape", onClick: function () { return onCancelShapeDraft === null || onCancelShapeDraft === void 0 ? void 0 : onCancelShapeDraft(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
81
81
  color: "#ccc",
82
82
  fontSize: ACTION_FONT_SIZE,
83
83
  textTransform: "none",
@@ -1,2 +1,7 @@
1
- export default EraserPlatte;
2
- declare function EraserPlatte(_a: any): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
1
+ export default function EraserPlatte({ expandEraseOptions, updateDrawPen, setDrawingEnabled, brushSize, updateBrushSize, }: {
2
+ expandEraseOptions: any;
3
+ updateDrawPen: any;
4
+ setDrawingEnabled: any;
5
+ brushSize?: number | undefined;
6
+ updateBrushSize: any;
7
+ }): import("react/jsx-runtime").JSX.Element;
@@ -1,29 +1,26 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
1
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
13
  import { Stack, IconButton } from "@mui/material";
3
14
  import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
4
15
  import FiberManualRecordOutlinedIcon from "@mui/icons-material/FiberManualRecordOutlined";
5
- var __assign = function () {
6
- __assign =
7
- Object.assign ||
8
- function (t) {
9
- for (var s, i = 1, n = arguments.length; i < n; i++) {
10
- s = arguments[i];
11
- for (var p in s)
12
- if (Object.prototype.hasOwnProperty.call(s, p))
13
- t[p] = s[p];
14
- }
15
- return t;
16
- };
17
- return __assign.apply(this, arguments);
18
- };
19
- var EraserPlatte = function (_a) {
20
- var expandEraseOptions = _a.expandEraseOptions, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled;
16
+ import { BrushSizeSlider } from "./BrushSizeSlider";
17
+ export default function EraserPlatte(_a) {
18
+ var expandEraseOptions = _a.expandEraseOptions, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.brushSize, brushSize = _b === void 0 ? 1 : _b, updateBrushSize = _a.updateBrushSize;
21
19
  var eraseOptions = [
22
20
  _jsx(FiberManualRecordIcon, { style: { color: "white" } }, "e0"),
23
21
  _jsx(FiberManualRecordOutlinedIcon, { style: { color: "white" } }, "e1"),
24
22
  ];
25
- return _jsxs(Stack, __assign({
26
- style: {
23
+ return (_jsxs(Stack, __assign({ style: {
27
24
  position: "absolute",
28
25
  top: "100%",
29
26
  left: 0,
@@ -36,21 +33,8 @@ var EraserPlatte = function (_a) {
36
33
  borderTopRightRadius: "6pt",
37
34
  background: "#333",
38
35
  width: 150
39
- },
40
- direction: "column"
41
- }, {
42
- children: [
43
- _jsx(Stack, __assign({ direction: "row", style: { justifyContent: "center" } }, {
44
- children: eraseOptions.map(function (value, index) {
45
- return _jsx(IconButton, __assign({
46
- onClick: function () {
47
- updateDrawPen({ target: { value: index === 0 ? 8 : 0 } });
48
- setDrawingEnabled(true);
49
- }
50
- }, { children: value }), index);
51
- })
52
- })),
53
- ]
54
- }));
55
- };
56
- export default EraserPlatte;
36
+ }, direction: "column" }, { children: [updateBrushSize && (_jsx(BrushSizeSlider, { label: "Eraser size", brushSize: brushSize, updateBrushSize: updateBrushSize })), _jsx(Stack, __assign({ direction: "row", style: { justifyContent: "center" } }, { children: eraseOptions.map(function (value, index) { return (_jsx(IconButton, __assign({ onClick: function () {
37
+ updateDrawPen({ target: { value: index === 0 ? 8 : 0 } });
38
+ setDrawingEnabled(true);
39
+ } }, { children: value }), index)); }) }))] })));
40
+ }
@@ -71,13 +71,15 @@ export function MroDrawToolkit(props) {
71
71
  var _d = useState(undefined), setMaskColor = _d[1];
72
72
  var filled = props.drawPen > 7;
73
73
  useEffect(function () {
74
- if (!props.shapeDraftActive)
74
+ if (!props.shapeDraftActive && !props.penDraftActive)
75
75
  return;
76
76
  if (drawShapeTool === "rectangle")
77
77
  setExpandedOption("r");
78
78
  else if (drawShapeTool === "ellipse")
79
79
  setExpandedOption("l");
80
- }, [props.shapeDraftActive, drawShapeTool]);
80
+ else if (drawShapeTool === "pen")
81
+ setExpandedOption("d");
82
+ }, [props.shapeDraftActive, props.penDraftActive, drawShapeTool]);
81
83
  var shapeSelectedSx = function (shape) {
82
84
  return drawShapeTool === shape
83
85
  ? { backgroundColor: "rgba(88, 15, 139, 0.12)", color: "#580f8b" }
@@ -149,7 +151,7 @@ export function MroDrawToolkit(props) {
149
151
  var _a;
150
152
  if (clickTargetIsNiivueCanvas(event.target))
151
153
  return;
152
- if (props.shapeDraftActive)
154
+ if (props.shapeDraftActive || props.penDraftActive)
153
155
  return;
154
156
  setExpandedOption("n");
155
157
  setExpandOpacityOptions(false);
@@ -170,7 +172,7 @@ export function MroDrawToolkit(props) {
170
172
  alignItems: "center",
171
173
  gap: 4,
172
174
  overflow: "visible"
173
- } }, { children: [_jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "d" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Pen" }, { children: _jsx(IconButton, __assign({ "aria-label": "pen", size: "small", onClick: clickPen, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("pen")) }, { children: _jsx(DrawIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "d", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, showPenModes: true, penDrawMode: props.penDrawMode, onPenDrawModeChange: props.onPenDrawModeChange, polylineVertexCount: props.polylineVertexCount, onCancelPolyline: props.onCancelPolyline, onFinishPolyline: props.onFinishPolyline, onCloseFillPolyline: props.onCloseFillPolyline })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "r" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Rectangle" }, { children: _jsx(IconButton, __assign({ "aria-label": "rectangle", size: "small", onClick: clickRectangle, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("rectangle")) }, { children: _jsx(CropSquareOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "r", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "rectangle", onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "l" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Ellipse" }, { children: _jsx(IconButton, __assign({ "aria-label": "ellipse", size: "small", onClick: clickEllipse, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("ellipse")) }, { children: _jsx(CircleOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "l", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "ellipse", onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "e" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Eraser" }, { children: _jsx(IconButton, __assign({ "aria-label": "erase", size: "small", onClick: clickEraser, sx: toolBtnSx }, { children: filled || expandedOption !== "e" ? (_jsx(EraserIcon, {})) : (_jsx(AutoFixNormalOutlinedIcon, { sx: { color: ICON_COLOR } })) })) })), _jsx(EraserPlatte, { expandEraseOptions: expandedOption === "e", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled })] })), _jsx(Tooltip, __assign({ title: "Undo" }, { children: _jsx(IconButton, __assign({ "aria-label": "revert", size: "small", onClick: function () { return props.drawUndo(); }, sx: toolBtnSx }, { children: _jsx(ReplyIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Tooltip, __assign({ title: "Save screenshot" }, { children: _jsx("span", { children: _jsx(IconButton, __assign({ "aria-label": "capture", size: "small", disabled: !vol, onClick: function () { return vol && props.nv.saveScene("".concat(vol.name, "_drawing.png")); }, sx: toolBtnSx }, { children: _jsx(CameraAltIcon, { sx: { color: ICON_COLOR } }) })) }) })), _jsx(Tooltip, __assign({ title: "Clear drawing" }, { children: _jsx(IconButton, __assign({ "aria-label": "delete", size: "small", onClick: function () {
175
+ } }, { children: [_jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "d" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Pen" }, { children: _jsx(IconButton, __assign({ "aria-label": "pen", size: "small", onClick: clickPen, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("pen")) }, { children: _jsx(DrawIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "d", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, showPenModes: true, penDrawMode: props.penDrawMode, onPenDrawModeChange: props.onPenDrawModeChange, polylineVertexCount: props.polylineVertexCount, onCancelPolyline: props.onCancelPolyline, penDraftActive: props.penDraftActive, onApplyPenDraft: props.onApplyPenDraft, onCancelPenDraft: props.onCancelPenDraft, onCloseFillPenDraft: props.onCloseFillPenDraft, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "r" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Rectangle" }, { children: _jsx(IconButton, __assign({ "aria-label": "rectangle", size: "small", onClick: clickRectangle, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("rectangle")) }, { children: _jsx(CropSquareOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "r", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "rectangle", onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "l" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Ellipse" }, { children: _jsx(IconButton, __assign({ "aria-label": "ellipse", size: "small", onClick: clickEllipse, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("ellipse")) }, { children: _jsx(CircleOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "l", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "ellipse", onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "e" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Eraser" }, { children: _jsx(IconButton, __assign({ "aria-label": "erase", size: "small", onClick: clickEraser, sx: toolBtnSx }, { children: filled || expandedOption !== "e" ? (_jsx(EraserIcon, {})) : (_jsx(AutoFixNormalOutlinedIcon, { sx: { color: ICON_COLOR } })) })) })), _jsx(EraserPlatte, { expandEraseOptions: expandedOption === "e", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsx(Tooltip, __assign({ title: "Undo" }, { children: _jsx(IconButton, __assign({ "aria-label": "revert", size: "small", onClick: function () { return props.drawUndo(); }, sx: toolBtnSx }, { children: _jsx(ReplyIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Tooltip, __assign({ title: "Save screenshot" }, { children: _jsx("span", { children: _jsx(IconButton, __assign({ "aria-label": "capture", size: "small", disabled: !vol, onClick: function () { return vol && props.nv.saveScene("".concat(vol.name, "_drawing.png")); }, sx: toolBtnSx }, { children: _jsx(CameraAltIcon, { sx: { color: ICON_COLOR } }) })) }) })), _jsx(Tooltip, __assign({ title: "Clear drawing" }, { children: _jsx(IconButton, __assign({ "aria-label": "delete", size: "small", onClick: function () {
174
176
  props.nv.clearDrawing();
175
177
  props.resampleImage();
176
178
  props.setDrawingChanged(false);
@@ -0,0 +1,74 @@
1
+ /** @typedef {'polyline' | 'freehand'} PenDraftKind */
2
+ /**
3
+ * @typedef {Object} PenDraft
4
+ * @property {PenDraftKind} kind
5
+ * @property {Uint8Array} baseBitmap
6
+ * @property {number} axCorSag
7
+ * @property {number} penValue
8
+ * @property {[number, number, number][]} [vertices]
9
+ * @property {[number, number, number][]} [strokeVoxels]
10
+ * @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
11
+ */
12
+ export function isFreehandPenActive(nv: any): any;
13
+ export function shouldDeferFreehandCommit(nv: any): any;
14
+ /**
15
+ * @typedef {Object} PenDraft
16
+ * @property {PenDraftKind} kind
17
+ * @property {Uint8Array} baseBitmap
18
+ * @property {number} axCorSag
19
+ * @property {number} penValue
20
+ * @property {[number, number, number][]} [vertices]
21
+ * @property {[number, number, number][]} [strokeVoxels]
22
+ * @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
23
+ */
24
+ export function redrawPolylineDraft(nv: any, draft: any): void;
25
+ export function translatePolylineVertices(vertices: any, delta: any): any;
26
+ export function updatePolylineVertex(vertices: any, index: any, newVox: any): any;
27
+ export function syncPolylineDraftToNv(nv: any, draft: any): void;
28
+ export function captureFreehandDraft(nv: any, sessionStartBitmap: any, axCorSag: any): {
29
+ kind: string;
30
+ baseBitmap: Uint8Array;
31
+ axCorSag: any;
32
+ penValue: any;
33
+ strokeVoxels: number[][];
34
+ bounds: {
35
+ x1: number;
36
+ y1: number;
37
+ z1: number;
38
+ x2: number;
39
+ y2: number;
40
+ z2: number;
41
+ };
42
+ } | null;
43
+ export function redrawFreehandDraft(nv: any, draft: any): void;
44
+ export function translateFreehandDraft(draft: any, delta: any): any;
45
+ export function resizeFreehandDraft(draft: any, cornerIndex: any, newVox: any): any;
46
+ export function boundsToFreehandCorners(bounds: any, axCorSag: any): any[][];
47
+ export function polylineDraftFromNv(nv: any): {
48
+ kind: string;
49
+ vertices: any;
50
+ baseBitmap: Uint8Array;
51
+ axCorSag: any;
52
+ penValue: any;
53
+ } | null;
54
+ export function applyPenDraft(nv: any, draft: any, { fillClosed }?: {
55
+ fillClosed?: boolean | undefined;
56
+ }): void;
57
+ export function cancelPenDraft(nv: any, draft: any): void;
58
+ export type PenDraftKind = 'polyline' | 'freehand';
59
+ export type PenDraft = {
60
+ kind: PenDraftKind;
61
+ baseBitmap: Uint8Array;
62
+ axCorSag: number;
63
+ penValue: number;
64
+ vertices?: [number, number, number][] | undefined;
65
+ strokeVoxels?: [number, number, number][] | undefined;
66
+ bounds?: {
67
+ x1: number;
68
+ y1: number;
69
+ x2: number;
70
+ y2: number;
71
+ z1: number;
72
+ z2: number;
73
+ } | undefined;
74
+ };
@@ -0,0 +1,261 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
13
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
14
+ if (ar || !(i in from)) {
15
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
16
+ ar[i] = from[i];
17
+ }
18
+ }
19
+ return to.concat(ar || Array.prototype.slice.call(from));
20
+ };
21
+ import { translatePt } from "./shapeDraftUtils";
22
+ import { NI_PEN_TYPE } from "./niivuePenType";
23
+ /** @typedef {'polyline' | 'freehand'} PenDraftKind */
24
+ /**
25
+ * @typedef {Object} PenDraft
26
+ * @property {PenDraftKind} kind
27
+ * @property {Uint8Array} baseBitmap
28
+ * @property {number} axCorSag
29
+ * @property {number} penValue
30
+ * @property {[number, number, number][]} [vertices]
31
+ * @property {[number, number, number][]} [strokeVoxels]
32
+ * @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
33
+ */
34
+ export function isFreehandPenActive(nv) {
35
+ return (nv.opts.isFilledPen &&
36
+ nv.opts.drawingEnabled &&
37
+ nv.opts.penType === NI_PEN_TYPE.PEN &&
38
+ !nv.opts.polylinePenMode);
39
+ }
40
+ export function shouldDeferFreehandCommit(nv) {
41
+ return !!nv.opts.deferFreehandCommit && isFreehandPenActive(nv);
42
+ }
43
+ /**
44
+ * @typedef {Object} PenDraft
45
+ * @property {PenDraftKind} kind
46
+ * @property {Uint8Array} baseBitmap
47
+ * @property {number} axCorSag
48
+ * @property {number} penValue
49
+ * @property {[number, number, number][]} [vertices]
50
+ * @property {[number, number, number][]} [strokeVoxels]
51
+ * @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
52
+ */
53
+ export function redrawPolylineDraft(nv, draft) {
54
+ var _a;
55
+ if (!((_a = draft === null || draft === void 0 ? void 0 : draft.vertices) === null || _a === void 0 ? void 0 : _a.length) || !draft.baseBitmap)
56
+ return;
57
+ nv.drawBitmap.set(draft.baseBitmap);
58
+ nv.drawPenAxCorSag = draft.axCorSag;
59
+ for (var i = 1; i < draft.vertices.length; i++) {
60
+ nv.drawPenLine(draft.vertices[i], draft.vertices[i - 1], draft.penValue);
61
+ }
62
+ nv.refreshDrawing(false, false);
63
+ nv.drawScene();
64
+ }
65
+ export function translatePolylineVertices(vertices, delta) {
66
+ return vertices.map(function (v) { return translatePt(v, delta); });
67
+ }
68
+ export function updatePolylineVertex(vertices, index, newVox) {
69
+ var next = vertices.map(function (v) { return __spreadArray([], v, true); });
70
+ next[index] = __spreadArray([], newVox, true);
71
+ return next;
72
+ }
73
+ export function syncPolylineDraftToNv(nv, draft) {
74
+ nv._cloudMrPolylineVertices = draft.vertices.map(function (v) { return __spreadArray([], v, true); });
75
+ if (nv.drawBitmap) {
76
+ nv._cloudMrPolylineBaseBitmap = nv.drawBitmap.slice();
77
+ }
78
+ nv._cloudMrPolylineAxCorSag = draft.axCorSag;
79
+ }
80
+ export function captureFreehandDraft(nv, sessionStartBitmap, axCorSag) {
81
+ var _a;
82
+ if (!nv.drawBitmap || !sessionStartBitmap)
83
+ return null;
84
+ var penValue = nv.opts.penValue;
85
+ var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
86
+ if (!dims)
87
+ return null;
88
+ var strokeVoxels = [];
89
+ for (var i = 0; i < nv.drawBitmap.length; i++) {
90
+ if (nv.drawBitmap[i] === penValue && sessionStartBitmap[i] !== penValue) {
91
+ var z = Math.floor(i / (dims[1] * dims[2]));
92
+ var rem = i - z * dims[1] * dims[2];
93
+ var y = Math.floor(rem / dims[1]);
94
+ var x = rem % dims[1];
95
+ strokeVoxels.push([x, y, z]);
96
+ }
97
+ }
98
+ if (strokeVoxels.length === 0)
99
+ return null;
100
+ var x1 = Infinity;
101
+ var y1 = Infinity;
102
+ var z1 = Infinity;
103
+ var x2 = -Infinity;
104
+ var y2 = -Infinity;
105
+ var z2 = -Infinity;
106
+ for (var _i = 0, strokeVoxels_1 = strokeVoxels; _i < strokeVoxels_1.length; _i++) {
107
+ var _b = strokeVoxels_1[_i], x = _b[0], y = _b[1], z = _b[2];
108
+ x1 = Math.min(x1, x);
109
+ y1 = Math.min(y1, y);
110
+ z1 = Math.min(z1, z);
111
+ x2 = Math.max(x2, x);
112
+ y2 = Math.max(y2, y);
113
+ z2 = Math.max(z2, z);
114
+ }
115
+ return {
116
+ kind: "freehand",
117
+ baseBitmap: new Uint8Array(sessionStartBitmap),
118
+ axCorSag: axCorSag,
119
+ penValue: penValue,
120
+ strokeVoxels: strokeVoxels,
121
+ bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
122
+ };
123
+ }
124
+ export function redrawFreehandDraft(nv, draft) {
125
+ var _a;
126
+ if (!((_a = draft === null || draft === void 0 ? void 0 : draft.strokeVoxels) === null || _a === void 0 ? void 0 : _a.length) || !draft.baseBitmap)
127
+ return;
128
+ nv.drawBitmap.set(draft.baseBitmap);
129
+ for (var _i = 0, _b = draft.strokeVoxels; _i < _b.length; _i++) {
130
+ var _c = _b[_i], x = _c[0], y = _c[1], z = _c[2];
131
+ nv.drawPt(x, y, z, draft.penValue);
132
+ }
133
+ nv.refreshDrawing(false, false);
134
+ nv.drawScene();
135
+ }
136
+ export function translateFreehandDraft(draft, delta) {
137
+ return __assign(__assign({}, draft), { strokeVoxels: draft.strokeVoxels.map(function (v) { return translatePt(v, delta); }), bounds: draft.bounds
138
+ ? {
139
+ x1: draft.bounds.x1 + delta[0],
140
+ y1: draft.bounds.y1 + delta[1],
141
+ z1: draft.bounds.z1 + delta[2],
142
+ x2: draft.bounds.x2 + delta[0],
143
+ y2: draft.bounds.y2 + delta[1],
144
+ z2: draft.bounds.z2 + delta[2]
145
+ }
146
+ : draft.bounds });
147
+ }
148
+ export function resizeFreehandDraft(draft, cornerIndex, newVox) {
149
+ if (!draft.bounds)
150
+ return draft;
151
+ var axCorSag = draft.axCorSag;
152
+ var b = __assign({}, draft.bounds);
153
+ var corners = boundsToFreehandCorners(b, axCorSag);
154
+ corners[cornerIndex] = __spreadArray([], newVox, true);
155
+ var xs = corners.map(function (c) { return c[0]; });
156
+ var ys = corners.map(function (c) { return c[1]; });
157
+ var zs = corners.map(function (c) { return c[2]; });
158
+ var nextBounds = {
159
+ x1: Math.min.apply(Math, xs),
160
+ y1: Math.min.apply(Math, ys),
161
+ z1: Math.min.apply(Math, zs),
162
+ x2: Math.max.apply(Math, xs),
163
+ y2: Math.max.apply(Math, ys),
164
+ z2: Math.max.apply(Math, zs)
165
+ };
166
+ var oldW = Math.max(1, b.x2 - b.x1);
167
+ var oldH = Math.max(1, b.y2 - b.y1);
168
+ var newW = Math.max(1, nextBounds.x2 - nextBounds.x1);
169
+ var newH = Math.max(1, nextBounds.y2 - nextBounds.y1);
170
+ var scaled = draft.strokeVoxels.map(function (v) {
171
+ var relH = (v[0] - b.x1) / oldW;
172
+ var relV = (v[1] - b.y1) / oldH;
173
+ if (axCorSag === 0) {
174
+ return [
175
+ Math.round(nextBounds.x1 + relH * newW),
176
+ Math.round(nextBounds.y1 + relV * newH),
177
+ v[2],
178
+ ];
179
+ }
180
+ if (axCorSag === 1) {
181
+ return [
182
+ Math.round(nextBounds.x1 + relH * newW),
183
+ v[1],
184
+ Math.round(nextBounds.y1 + relV * newH),
185
+ ];
186
+ }
187
+ return [
188
+ v[0],
189
+ Math.round(nextBounds.x1 + relH * newW),
190
+ Math.round(nextBounds.y1 + relV * newH),
191
+ ];
192
+ });
193
+ return __assign(__assign({}, draft), { strokeVoxels: scaled, bounds: nextBounds });
194
+ }
195
+ export function boundsToFreehandCorners(bounds, axCorSag) {
196
+ var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
197
+ if (axCorSag === 0) {
198
+ var z = Math.round((z1 + z2) / 2);
199
+ return [
200
+ [x1, y1, z],
201
+ [x2, y1, z],
202
+ [x1, y2, z],
203
+ [x2, y2, z],
204
+ ];
205
+ }
206
+ if (axCorSag === 1) {
207
+ var y = Math.round((y1 + y2) / 2);
208
+ return [
209
+ [x1, y, z1],
210
+ [x2, y, z1],
211
+ [x1, y, z2],
212
+ [x2, y, z2],
213
+ ];
214
+ }
215
+ var x = Math.round((x1 + x2) / 2);
216
+ return [
217
+ [x, y1, z1],
218
+ [x, y2, z1],
219
+ [x, y1, z2],
220
+ [x, y2, z2],
221
+ ];
222
+ }
223
+ export function polylineDraftFromNv(nv) {
224
+ var vertices = nv._cloudMrPolylineVertices;
225
+ var baseBitmap = nv._cloudMrPolylineSessionStartBitmap;
226
+ if (!(vertices === null || vertices === void 0 ? void 0 : vertices.length) || vertices.length < 2 || !baseBitmap)
227
+ return null;
228
+ return {
229
+ kind: "polyline",
230
+ vertices: vertices.map(function (v) { return __spreadArray([], v, true); }),
231
+ baseBitmap: new Uint8Array(baseBitmap),
232
+ axCorSag: nv._cloudMrPolylineAxCorSag,
233
+ penValue: nv.opts.penValue
234
+ };
235
+ }
236
+ export function applyPenDraft(nv, draft, _a) {
237
+ var _b = _a === void 0 ? {} : _a, _c = _b.fillClosed, fillClosed = _c === void 0 ? false : _c;
238
+ if (draft.kind === "polyline") {
239
+ redrawPolylineDraft(nv, draft);
240
+ if (fillClosed && draft.vertices.length >= 3) {
241
+ nv.drawPenAxCorSag = draft.axCorSag;
242
+ nv.drawPenLine(draft.vertices[0], draft.vertices[draft.vertices.length - 1], draft.penValue);
243
+ nv.drawPenFillPts = draft.vertices.map(function (v) { return __spreadArray([], v, true); });
244
+ nv.drawPenFilled();
245
+ }
246
+ }
247
+ else {
248
+ redrawFreehandDraft(nv, draft);
249
+ }
250
+ nv.drawAddUndoBitmap(nv.drawFillOverwrites);
251
+ if (typeof nv.onDrawingChanged === "function") {
252
+ nv.onDrawingChanged("draw");
253
+ }
254
+ }
255
+ export function cancelPenDraft(nv, draft) {
256
+ if (draft === null || draft === void 0 ? void 0 : draft.baseBitmap) {
257
+ nv.drawBitmap.set(draft.baseBitmap);
258
+ nv.refreshDrawing(true, false);
259
+ nv.drawScene();
260
+ }
261
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmr-ux",
3
- "version": "4.4.9",
3
+ "version": "4.5.2",
4
4
  "author": "erosmontin@gmail.com",
5
5
  "license": "MIT",
6
6
  "repository": "erosmontin/cloudmr-ux",