diff --git a/public/sprite.svg b/public/sprite.svg
index c01baef0..93f56aec 100644
--- a/public/sprite.svg
+++ b/public/sprite.svg
@@ -197,8 +197,14 @@
-
-
+
+
+
+
+
+
+
+
diff --git a/src/engine/Graphics2d.js b/src/engine/Graphics2d.js
index a46d90cb..ae7d04b6 100644
--- a/src/engine/Graphics2d.js
+++ b/src/engine/Graphics2d.js
@@ -166,10 +166,6 @@ class Graphics2d extends React.Component {
let wScreen = 0,
hScreen = 0;
- const xPos = store.render2dxPos;
- const yPos = store.render2dyPos;
- const zoom = store.render2dZoom;
-
if (mode2d === Modes2d.TRANSVERSE) {
// calc screen rect based on physics volume slice size (z slice)
const xyRratio = pbox.x / pbox.y;
@@ -218,17 +214,15 @@ class Graphics2d extends React.Component {
let zSlice = Math.floor(zDim * sliceRatio);
zSlice = zSlice < zDim ? zSlice : zDim - 1;
const zOff = zSlice * xyDim;
- const xStep = (zoom * xDim) / wScreen;
- const yStep = (zoom * yDim) / hScreen;
+ const xStep = xDim / wScreen;
+ const yStep = yDim / hScreen;
let j = 0;
- let ay = yPos * yDim;
if (vol.m_bytesPerVoxel === ONE) {
- for (let y = 0; y < hScreen; y++, ay += yStep) {
- const ySrc = Math.floor(ay);
+ for (let y = 0; y < hScreen; y++) {
+ const ySrc = Math.floor(y * yStep);
const yOff = ySrc * xDim;
- let ax = xPos * xDim;
- for (let x = 0; x < wScreen; x++, ax += xStep) {
- const xSrc = Math.floor(ax);
+ for (let x = 0; x < wScreen; x++) {
+ const xSrc = Math.floor(x * xStep);
const val = dataSrc[zOff + yOff + xSrc];
dataDst[j + 0] = val;
dataDst[j + 1] = val;
@@ -238,12 +232,11 @@ class Graphics2d extends React.Component {
} // for (x)
} // for (y)
} else if (vol.m_bytesPerVoxel === FOUR) {
- for (let y = 0; y < hScreen; y++, ay += yStep) {
- const ySrc = Math.floor(ay);
+ for (let y = 0; y < hScreen; y++) {
+ const ySrc = Math.floor(y * yStep);
const yOff = ySrc * xDim;
- let ax = xPos * xDim;
- for (let x = 0; x < wScreen; x++, ax += xStep) {
- const xSrc = Math.floor(ax);
+ for (let x = 0; x < wScreen; x++) {
+ const xSrc = Math.floor(x * xStep);
const val = dataSrc[(zOff + yOff + xSrc) * FOUR + OFF_3];
const val4 = val * FOUR;
const rCol = roiPal256[val4 + 0];
@@ -305,17 +298,15 @@ class Graphics2d extends React.Component {
let xSlice = Math.floor(xDim * sliceRatio);
xSlice = xSlice < xDim ? xSlice : xDim - 1;
- const yStep = (zoom * yDim) / wScreen;
- const zStep = (zoom * zDim) / hScreen;
+ const yStep = yDim / wScreen;
+ const zStep = zDim / hScreen;
let j = 0;
- let az = yPos * zDim;
if (vol.m_bytesPerVoxel === ONE) {
- for (let y = 0; y < hScreen; y++, az += zStep) {
- const zSrc = Math.floor(az);
+ for (let y = 0; y < hScreen; y++) {
+ const zSrc = Math.floor(y * zStep);
const zOff = zSrc * xDim * yDim;
- let ay = xPos * yDim;
- for (let x = 0; x < wScreen; x++, ay += yStep) {
- const ySrc = Math.floor(ay);
+ for (let x = 0; x < wScreen; x++) {
+ const ySrc = Math.floor(x * yStep);
const yOff = ySrc * xDim;
const val = dataSrc[zOff + yOff + xSlice];
@@ -328,12 +319,11 @@ class Graphics2d extends React.Component {
} // for (x)
} // for (y)
} else if (vol.m_bytesPerVoxel === FOUR) {
- for (let y = 0; y < hScreen; y++, az += zStep) {
- const zSrc = Math.floor(az);
+ for (let y = 0; y < hScreen; y++) {
+ const zSrc = Math.floor(y * zStep);
const zOff = zSrc * xDim * yDim;
- let ay = xPos * yDim;
- for (let x = 0; x < wScreen; x++, ay += yStep) {
- const ySrc = Math.floor(ay);
+ for (let x = 0; x < wScreen; x++) {
+ const ySrc = Math.floor(x * yStep);
const yOff = ySrc * xDim;
const val = dataSrc[(zOff + yOff + xSlice) * FOUR + OFF_3];
const val4 = val * FOUR;
@@ -398,17 +388,15 @@ class Graphics2d extends React.Component {
ySlice = ySlice < yDim ? ySlice : yDim - 1;
const yOff = ySlice * xDim;
- const xStep = (zoom * xDim) / wScreen;
- const zStep = (zoom * zDim) / hScreen;
+ const xStep = xDim / wScreen;
+ const zStep = zDim / hScreen;
let j = 0;
- let az = yPos * zDim;
if (vol.m_bytesPerVoxel === ONE) {
- for (let y = 0; y < hScreen; y++, az += zStep) {
- const zSrc = Math.floor(az);
+ for (let y = 0; y < hScreen; y++) {
+ const zSrc = Math.floor(y * zStep);
const zOff = zSrc * xDim * yDim;
- let ax = xPos * xDim;
- for (let x = 0; x < wScreen; x++, ax += xStep) {
- const xSrc = Math.floor(ax);
+ for (let x = 0; x < wScreen; x++) {
+ const xSrc = Math.floor(x * xStep);
const val = dataSrc[zOff + yOff + xSrc];
dataDst[j + 0] = val;
@@ -420,12 +408,11 @@ class Graphics2d extends React.Component {
} // for (x)
} // for (y)
} else if (vol.m_bytesPerVoxel === FOUR) {
- for (let y = 0; y < hScreen; y++, az += zStep) {
- const zSrc = Math.floor(az);
+ for (let y = 0; y < hScreen; y++) {
+ const zSrc = Math.floor(y * zStep);
const zOff = zSrc * xDim * yDim;
- let ax = xPos * xDim;
- for (let x = 0; x < wScreen; x++, ax += xStep) {
- const xSrc = Math.floor(ax);
+ for (let x = 0; x < wScreen; x++) {
+ const xSrc = Math.floor(x * xStep);
const val = dataSrc[(zOff + yOff + xSrc) * FOUR + OFF_3];
const val4 = val * FOUR;
const rCol = roiPal256[val4 + 0];
@@ -461,20 +448,26 @@ class Graphics2d extends React.Component {
}
renderReadyImage() {
+ const objCanvas = this.m_mount.current;
+ const ctx = objCanvas.getContext('2d');
+ const store = this.props;
+ const zoom = store.render2dZoom;
+ const xPos = store.render2dxPos;
+ const yPos = store.render2dyPos;
+ const canvasWidth = objCanvas.width;
+ const canvasHeight = objCanvas.height;
+ const newImgWidth = canvasWidth / zoom;
+ const newImgHeight = canvasHeight / zoom;
+
if (!this.m_isMounted) {
return;
}
-
- const objCanvas = this.m_mount.current;
if (objCanvas === null) {
return;
}
- const ctx = objCanvas.getContext('2d');
// prepare canvas
this.fillBackground(ctx);
- const store = this.props;
-
const volSet = store.volumeSet;
if (volSet.getNumVolumes() === 0) {
return;
@@ -491,35 +484,60 @@ class Graphics2d extends React.Component {
const h = this.m_toolPick.m_hScreen;
this.segm2d.render(ctx, w, h, this.imgData);
} else {
- ctx.putImageData(this.imgData, 0, 0);
+ createImageBitmap(this.imgData)
+ .then((imageBitmap) => {
+ ctx.drawImage(imageBitmap, xPos, yPos, canvasWidth, canvasHeight, 0, 0, newImgWidth, newImgHeight);
+ })
+ .then(() => {
+ this.m_toolPick.render(ctx);
+ this.m_toolDistance.render(ctx, store);
+ this.m_toolAngle.render(ctx, store);
+ this.m_toolArea.render(ctx, store);
+ this.m_toolRect.render(ctx, store);
+ this.m_toolText.render(ctx, store);
+ this.m_toolEdit.render(ctx, store);
+ this.m_toolDelete.render(ctx, store);
+ });
}
-
- // render all tools
- this.m_toolPick.render(ctx);
- this.m_toolDistance.render(ctx, store);
- this.m_toolAngle.render(ctx, store);
- this.m_toolArea.render(ctx, store);
- this.m_toolRect.render(ctx, store);
- this.m_toolText.render(ctx, store);
- this.m_toolEdit.render(ctx, store);
- this.m_toolDelete.render(ctx, store);
}
onMouseWheel(evt) {
+ const objCanvas = this.m_mount.current;
+ const canvasRect = objCanvas.getBoundingClientRect();
+ let xPosNew;
+ let yPosNew;
const store = this.props;
+ const zoom = store.render2dZoom;
const step = evt.deltaY * 2 ** -10;
+ let newZoom = zoom + step;
- const zoom = store.render2dZoom;
- let zoomNew = zoom + step;
- let xPosNew = store.render2dxPos - ((step / 4) * evt.clientX) / evt.clientY;
- let yPosNew = store.render2dyPos - ((step / 4) * evt.clientY) / evt.clientX;
+ if (step < 0) {
+ const mouseX = (evt.clientX - canvasRect.left) * zoom + store.render2dxPos;
+ const mouseY = (evt.clientY - canvasRect.top) * zoom + store.render2dyPos;
+ xPosNew = mouseX - (mouseX - store.render2dxPos) * (newZoom / zoom);
+ yPosNew = mouseY - (mouseY - store.render2dyPos) * (newZoom / zoom);
+ } else {
+ const initialX = canvasRect.width * zoom + store.render2dxPos;
+ const initialY = canvasRect.height * zoom + store.render2dyPos;
+ xPosNew = initialX - (initialX - store.render2dxPos) * (newZoom / zoom);
+ yPosNew = initialY - (initialY - store.render2dyPos) * (newZoom / zoom);
+ }
- console.log(`onMouseWheel.evt = ${xPosNew}, ${yPosNew}`);
- // console.log(`onMouseWheel. zoom.puml = ${zoom.puml} zoomNew = ${zoomNew}, xyPos = ${xPosNew},${yPosNew}`);
- if (Math.abs(zoomNew) > 1 || Math.abs(zoomNew) < 0.02 || xPosNew < 0 || yPosNew < 0 || xPosNew > 1 || yPosNew > 1) {
+ if (xPosNew < 0) {
+ xPosNew = 0;
+ }
+ if (yPosNew < 0) {
+ yPosNew = 0;
+ }
+ if (newZoom > 1) {
+ newZoom = 1;
+ xPosNew = 0;
+ yPosNew = 0;
+ }
+ if (newZoom < 0.1) {
return;
}
- store.dispatch({ type: StoreActionType.SET_2D_ZOOM, render2dZoom: zoomNew });
+ store.dispatch({ type: StoreActionType.SET_2D_ZOOM, render2dZoom: newZoom });
store.dispatch({ type: StoreActionType.SET_2D_X_POS, render2dxPos: xPosNew });
store.dispatch({ type: StoreActionType.SET_2D_Y_POS, render2dyPos: yPosNew });
diff --git a/src/engine/tools2d/ToolDistance.js b/src/engine/tools2d/ToolDistance.js
index 254832b5..a77bc873 100644
--- a/src/engine/tools2d/ToolDistance.js
+++ b/src/engine/tools2d/ToolDistance.js
@@ -111,9 +111,11 @@ class ToolDistance {
const xDim = vol.m_xDim;
const yDim = vol.m_yDim;
const zDim = vol.m_zDim;
+ const objCanvas = store.graphics2d.m_mount.current;
+ const canvasRect = objCanvas.getBoundingClientRect();
const zoom = store.render2dZoom;
- const xPos = store.render2dxPos;
- const yPos = store.render2dyPos;
+ const xPos = store.render2dxPos / canvasRect.width;
+ const yPos = store.render2dyPos / canvasRect.height;
const vTex = {
x: 0.0,
@@ -148,9 +150,11 @@ class ToolDistance {
const xDim = vol.m_xDim;
const yDim = vol.m_yDim;
const zDim = vol.m_zDim;
+ const objCanvas = store.graphics2d.m_mount.current;
+ const canvasRect = objCanvas.getBoundingClientRect();
const zoom = store.render2dZoom;
- const xPos = store.render2dxPos;
- const yPos = store.render2dyPos;
+ const xPos = store.render2dxPos / canvasRect.width;
+ const yPos = store.render2dyPos / canvasRect.height;
if (mode2d === Modes2d.TRANSVERSE) {
// z const
vScr.x = (xTex / xDim - xPos) / zoom;
diff --git a/src/engine/tools2d/ToolPick.js b/src/engine/tools2d/ToolPick.js
index 0bc5ffe5..1de1152d 100644
--- a/src/engine/tools2d/ToolPick.js
+++ b/src/engine/tools2d/ToolPick.js
@@ -58,9 +58,11 @@ class ToolPick {
const xDim = vol.m_xDim;
const yDim = vol.m_yDim;
const zDim = vol.m_zDim;
+ const objCanvas = store.graphics2d.m_mount.current;
+ const canvasRect = objCanvas.getBoundingClientRect();
const zoom = store.render2dZoom;
- const xPos = store.render2dxPos;
- const yPos = store.render2dyPos;
+ const xPos = store.render2dxPos / canvasRect.width;
+ const yPos = store.render2dyPos / canvasRect.height;
if (mode2d === Modes2d.TRANSVERSE) {
// z: const
vTex.x = Math.floor((xPos + xScr * zoom) * xDim);
diff --git a/src/engine/tools2d/ToolTypes.js b/src/engine/tools2d/ToolTypes.js
index f4450d20..590aad0f 100644
--- a/src/engine/tools2d/ToolTypes.js
+++ b/src/engine/tools2d/ToolTypes.js
@@ -19,9 +19,10 @@ const Tools2dType = {
EDIT: 6,
DELETE: 7,
CLEAR: 8,
- ZOOM: 9,
- ZOOM_100: 10,
- FILTER: 11,
- NONE: 12,
+ ZOOM_IN: 9,
+ ZOOM_OUT: 10,
+ ZOOM_100: 11,
+ FILTER: 12,
+ NONE: 13,
};
export default Tools2dType;
diff --git a/src/ui/Main.module.css b/src/ui/Main.module.css
index 5c2cbbd3..b5f8a3fe 100644
--- a/src/ui/Main.module.css
+++ b/src/ui/Main.module.css
@@ -67,7 +67,7 @@
height: 50px;
position: absolute;
right: 0;
- top:50%;
+ top: 50%;
z-index: 10;
}
.left {
@@ -75,13 +75,13 @@
}
.center div {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 1;
- user-select: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1;
+ user-select: none;
}
.settings {
@@ -89,7 +89,6 @@
}
@media screen and (min-width: 768px) {
-
.header {
display: flex;
position: relative;
@@ -103,7 +102,7 @@
margin-top: 1rem;
}
- .header__panels {
+ .header__panels {
display: flex;
flex-direction: column;
width: 25rem;
@@ -156,20 +155,21 @@
}
@media screen and (min-width: 1024px) {
-
.header {
align-items: flex-start;
}
- .header div {
- margin-bottom: 1rem;
+ .header div {
+ height: auto;
+ padding-bottom: 0;
+ margin-bottom: 0;
}
.header__right {
align-items: stretch;
}
- .header__panels {
+ .header__panels {
width: 100%;
display: flex;
flex-direction: row;
@@ -203,11 +203,9 @@
.left {
top: 8%;
}
-
}
@media screen and (min-width: 1024px) and (orientation: landscape) {
-
.header__panels {
margin-right: 3rem;
}
@@ -218,13 +216,11 @@
}
.left {
- top:10%;
+ top: 10%;
}
-
}
@media screen and (min-width: 1024px) and (orientation: portrait) {
-
.header__panels {
flex-wrap: wrap;
}
@@ -232,19 +228,15 @@
.settings {
top: 4rem;
}
-
}
@media screen and (min-width: 1440px) {
.header__panels {
flex-wrap: nowrap;
}
-
}
-
@media screen and (min-width: 2560px) and (orientation: landscape) {
-
.left {
top: 5%;
}
diff --git a/src/ui/UiZoomTools.jsx b/src/ui/UiZoomTools.jsx
index 17fcc57d..e9be4ffe 100644
--- a/src/ui/UiZoomTools.jsx
+++ b/src/ui/UiZoomTools.jsx
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import Tools2dType from '../engine/tools2d/ToolTypes';
@@ -12,28 +12,76 @@ import { buttonsBuilder } from './Button/Button';
import { Container } from './Layout/Container';
const UiZoomTools = (props) => {
+ const MIN_ZOOM_THRESHOLD = 0.8;
const [activeButton, setActiveButton] = useState(Tools2dType.NONE);
+ const zoomImage = (step, buttonId) => {
+ const currentZoom = props.render2dZoom;
+ let newZoom = Math.round((currentZoom + step) * 10) / 10;
+ const objCanvas = props.graphics2d.m_mount.current;
+ const canvasRect = objCanvas.getBoundingClientRect();
+ let xPosNew;
+ let yPosNew;
+
+ if (buttonId === Tools2dType.ZOOM_IN && newZoom > 0) {
+ xPosNew = props.render2dxPos + (canvasRect.width / 2) * Math.abs(step);
+ yPosNew = props.render2dyPos + (canvasRect.height / 2) * Math.abs(step);
+ } else if (buttonId === Tools2dType.ZOOM_OUT && newZoom < 1) {
+ const initialX = canvasRect.width * currentZoom + props.render2dxPos;
+ const initialY = canvasRect.height * currentZoom + props.render2dyPos;
+ xPosNew = initialX - (initialX - props.render2dxPos) * (newZoom / currentZoom);
+ yPosNew = initialY - (initialY - props.render2dyPos) * (newZoom / currentZoom);
+ }
+
+ if (xPosNew < 0) {
+ xPosNew = 0;
+ }
+ if (yPosNew < 0) {
+ yPosNew = 0;
+ }
+ if (newZoom > 1) {
+ newZoom = 1;
+ xPosNew = 0;
+ yPosNew = 0;
+ }
+ if (newZoom < 0.1) {
+ return;
+ }
+ props.dispatch({ type: StoreActionType.SET_2D_ZOOM, render2dZoom: newZoom });
+ props.dispatch({ type: StoreActionType.SET_2D_X_POS, render2dxPos: xPosNew });
+ props.dispatch({ type: StoreActionType.SET_2D_Y_POS, render2dyPos: yPosNew });
+ };
+
const mediator = (buttonId) => {
setActiveButton(buttonId);
props.dispatch({ type: StoreActionType.SET_2D_TOOLS_INDEX, indexTools2d: buttonId });
- if (buttonId === Tools2dType.ZOOM_100) {
+ if (buttonId === Tools2dType.ZOOM_100 || (buttonId === Tools2dType.ZOOM_OUT && props.render2dZoom > MIN_ZOOM_THRESHOLD)) {
props.dispatch({ type: StoreActionType.SET_2D_ZOOM, render2dZoom: 1.0 });
props.dispatch({ type: StoreActionType.SET_2D_X_POS, render2dxPos: 0.0 });
props.dispatch({ type: StoreActionType.SET_2D_Y_POS, render2dyPos: 0.0 });
-
- props.graphics2d.forceUpdate();
- props.graphics2d.forceRender();
+ } else {
+ zoomImage(buttonId === Tools2dType.ZOOM_IN ? -0.1 : 0.1, buttonId);
}
};
+ useEffect(() => {
+ props.graphics2d.forceUpdate();
+ props.graphics2d.forceRender();
+ }, [props.render2dZoom]);
+
const buttons = [
{
- icon: 'zoom',
- caption: 'Zoom in/out',
- handler: mediator.bind(null, Tools2dType.ZOOM),
- id: Tools2dType.ZOOM,
+ icon: 'zoom_in',
+ caption: 'Zoom in',
+ handler: mediator.bind(null, Tools2dType.ZOOM_IN),
+ id: Tools2dType.ZOOM_IN,
+ },
+ {
+ icon: 'zoom_out',
+ caption: 'Zoom out',
+ handler: mediator.bind(null, Tools2dType.ZOOM_OUT),
+ id: Tools2dType.ZOOM_OUT,
},
{
icon: 'zoom_100',