From 801932671bb493386252b603a963f641225f0e79 Mon Sep 17 00:00:00 2001 From: James Petts Date: Fri, 15 Mar 2019 10:53:07 +0000 Subject: [PATCH] perf(BrushTool, drawBrush): Delete unused brush pixelData. Fix rendering flicker. Now when you erase pixels from a pixelData mask, the pixelData memory is freed up if you erase the last pixel of that mask on the slice. This is also makes it easier for tools like dcmjs to just hoover up all the slices that have non-blank masks on them. Secondly I fixed a small flicker edge case with the brush imageBitmapCache. --- src/tools/brush/BrushTool.js | 38 +++++++++++++++++++++++++----------- src/util/brush/drawBrush.js | 20 ++++++++++--------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/tools/brush/BrushTool.js b/src/tools/brush/BrushTool.js index e9c2aedd5..2c33dfa5f 100644 --- a/src/tools/brush/BrushTool.js +++ b/src/tools/brush/BrushTool.js @@ -9,7 +9,8 @@ import brushUtils from './../../util/brush/index.js'; import EVENTS from '../../events.js'; const { drawBrushPixels, getCircle } = brushUtils; -const { state } = store.modules.brush; + +const brushModule = store.modules.brush; /** * @public @@ -70,10 +71,10 @@ export default class BrushTool extends BaseBrushTool { } // Draw the hover overlay on top of the pixel data - const radius = state.radius; + const radius = brushModule.state.radius; const context = eventData.canvasContext; const element = eventData.element; - const drawId = state.drawColorId; + const drawId = brushModule.state.drawColorId; const color = this._getBrushColor(drawId); context.setTransform(1, 0, 0, 1, 0, 0); @@ -141,7 +142,7 @@ function _overlappingStrategy(evt) { return; } - const radius = state.radius; + const radius = brushModule.state.radius; const pointerArray = getCircle(radius, rows, columns, x, y); _drawMainColor(eventData, toolData, pointerArray); @@ -167,13 +168,13 @@ function _nonOverlappingStrategy(evt) { } const toolData = toolState.data; - const segmentationIndex = state.drawColorId; + const segmentationIndex = brushModule.state.drawColorId; if (x < 0 || x > columns || y < 0 || y > rows) { return; } - const radius = state.radius; + const radius = brushModule.state.radius; const pointerArray = getCircle(radius, rows, columns, x, y); const numberOfColors = BaseBrushTool.getNumberOfColors(); @@ -185,7 +186,7 @@ function _nonOverlappingStrategy(evt) { } if (toolData[i] && toolData[i].pixelData) { - drawBrushPixels(pointerArray, toolData[i].pixelData, columns, true); + drawBrushPixels(pointerArray, toolData[i], columns, true); toolData[i].invalidated = true; } } @@ -196,7 +197,7 @@ function _nonOverlappingStrategy(evt) { function _drawMainColor(eventData, toolData, pointerArray) { const shouldErase = _isCtrlDown(eventData); const columns = eventData.image.columns; - const segmentationIndex = state.drawColorId; + const segmentationIndex = brushModule.state.drawColorId; if (shouldErase && !toolData[segmentationIndex]) { // Erase command, yet no data yet, just return. @@ -208,17 +209,32 @@ function _drawMainColor(eventData, toolData, pointerArray) { } if (!toolData[segmentationIndex].pixelData) { + const enabledElement = external.cornerstone.getEnabledElement( + eventData.element + ); + const enabledElementUID = enabledElement.uuid; + + // Clear cache for this color to avoid flickering. + const imageBitmapCacheForElement = brushModule.getters.imageBitmapCacheForElement( + enabledElementUID + ); + + if (imageBitmapCacheForElement) { + imageBitmapCacheForElement[segmentationIndex] = null; + } + + // Add a new pixelData array. toolData[segmentationIndex].pixelData = new Uint8ClampedArray( eventData.image.width * eventData.image.height ); } - const pixelData = toolData[segmentationIndex].pixelData; + const toolDataI = toolData[segmentationIndex]; // Draw / Erase the active color. - drawBrushPixels(pointerArray, pixelData, columns, shouldErase); + drawBrushPixels(pointerArray, toolDataI, columns, shouldErase); - toolData[segmentationIndex].invalidated = true; + toolDataI.invalidated = true; } function _isCtrlDown(eventData) { diff --git a/src/util/brush/drawBrush.js b/src/util/brush/drawBrush.js index 42ccba665..f9b3e7639 100644 --- a/src/util/brush/drawBrush.js +++ b/src/util/brush/drawBrush.js @@ -7,24 +7,26 @@ import { draw, fillBox } from '../../drawing/index.js'; * @name drawBrushPixels * * @param {Object[]} pointerArray The array of points to draw. - * @param {number[]} storedPixels The brush mask to modify. - * @param {number} columns The number of columns in the mask. + * @param {number[]} toolData The cornerstoneTools annotation to modify. + * @param {number} columns The number of columns in the mask. * @param {boolean} [shouldErase = false] If true the modified mask pixels will be set to 0, rather than 1. * @returns {void} */ -function drawBrushPixels( - pointerArray, - storedPixels, - columns, - shouldErase = false -) { +function drawBrushPixels(pointerArray, toolData, columns, shouldErase = false) { const getPixelIndex = (x, y) => y * columns + x; + const pixelData = toolData.pixelData; + pointerArray.forEach(point => { const spIndex = getPixelIndex(point[0], point[1]); - storedPixels[spIndex] = shouldErase ? 0 : 1; + pixelData[spIndex] = shouldErase ? 0 : 1; }); + + // If Erased the last pixel, delete the pixelData array. + if (shouldErase && !pixelData.some(element => element !== 0)) { + delete toolData.pixelData; + } } /**