From c59584ded1ff0cbbfdcc89ca5f360af8b8caa451 Mon Sep 17 00:00:00 2001 From: Joe DF <3848219+joedf@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:09:16 -0500 Subject: [PATCH] Add Brightness / Contrast controls (#43) * add B/C controls, WIP for resulting images * apply B/C to resulting images make ComputeProbeValue_gs() B/C aware and use Konva's built-in filters * add btn to reset B/C * gui B/C: dont use listen() to allow num. type/edit * add GlobalBC option for independent b/c control * auto position opts window in bottom right * minor clean up --- app/index.html | 27 +++++++++++++++++++---- app/src/css/style.css | 5 +++-- app/src/main.js | 33 ++++++++++++++++++++++++++++ app/src/utils.js | 51 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/app/index.html b/app/index.html index c994b45..0aaf368 100644 --- a/app/index.html +++ b/app/index.html @@ -87,6 +87,8 @@ var G_GUI_Controller = new function() { this.pixelCountX = 8; this.pixelCountY = 8; + this.brightness = 0; + this.contrast = 0; this.digitalMag = "1.00x"; this.advancedMode = false; this.updateViews = function(){ @@ -95,6 +97,14 @@ G_Update_GroundTruth(); G_Update_InfoDisplays(); }; + this.updateFilters = function(){ G_UpdateFilters(); }, + this.globalBC = true; + this.resetBC = function(){ + let cGui = G_GUI_Controller.controls; + cGui.brightness.setValue(0); + cGui.contrast.setValue(0); + G_UpdateFilters(); + }, this.groundTruthImg = 'grains2tl.png'; this.pause_vSEM = G_VSEM_PAUSED; this.doImageMetric = G_IMG_METRIC_ENABLED; @@ -114,6 +124,16 @@ var gui_ip = gui.addFolder('Imaging Parameters'); gui_ip.add(G_GUI_Controller, 'pixelCountX', 1, 64, 1).onChange(G_GUI_Controller.updateViews); gui_ip.add(G_GUI_Controller, 'pixelCountY', 1, 64, 1).onChange(G_GUI_Controller.updateViews); + G_GUI_Controller.controls.brightness = gui_ip + .add(G_GUI_Controller, 'brightness', -1, 1, 0.01) + .onChange(G_GUI_Controller.updateFilters); + G_GUI_Controller.controls.contrast = gui_ip + .add(G_GUI_Controller, 'contrast', -100, 100, 0.1) + .onChange(G_GUI_Controller.updateFilters); + gui_ip.add(G_GUI_Controller, 'globalBC') + .name('Global B/C') + .onChange(G_GUI_Controller.updateFilters); + gui_ip.add(G_GUI_Controller, 'resetBC').name('Reset Brightness / Contrast'); gui_ip.add(G_GUI_Controller, 'digitalMag').listen(); G_GUI_Controller.controls.pixelSize_nm = gui_ip .add(G_GUI_Controller, 'pixelSize_nm', 1, 1000, 1).onChange(function(){ @@ -157,10 +177,9 @@ aboutBtn.name("About " + G_APP_NAME + " / Credits"); gui_io.open(); - $("#options").append(gui.domElement).draggable({ - // containment: 'body', - handle: '#options-handle', - }); + $("#options").append(gui.domElement) + .position({at:'right bottom', my:'right bottom', of: '#options-anchor'}) + .draggable({handle: '#options-handle'}); // used to auto-name the export files with a counter var G_Export_img_count = 0; diff --git a/app/src/css/style.css b/app/src/css/style.css index 5df346b..a96cda2 100644 --- a/app/src/css/style.css +++ b/app/src/css/style.css @@ -104,13 +104,14 @@ summary { #options-anchor { display: inline-block; position: fixed; - top: 35vh; - right: 0.25em; + bottom: 0; + right: 0; z-index: 69; user-select: none; cursor: move; width: 0; height: 0; + margin: 2em 1em; } #options-full-resample { diff --git a/app/src/main.js b/app/src/main.js index 858a00d..1911cfd 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -15,6 +15,7 @@ * G_Update_InfoDisplays * G_update_ImgMetrics * G_UpdateRuler + * G_UpdateFilters * G_AUTO_PREVIEW_LIMIT * G_VSEM_PAUSED * G_IMG_METRIC_ENABLED @@ -88,6 +89,8 @@ var G_Update_InfoDisplays = null; var G_update_ImgMetrics = null; /** global reference to update the ruler */ var G_UpdateRuler = null; +/** global reference to update/apply image filters */ +var G_UpdateFilters = null; /** a global reference to the main body container that holds the boxes/stages. * @todo do we still need this? Maybe remove... */ @@ -376,6 +379,36 @@ function OnImageLoaded(eImg, stages){ doUpdate(); }); + function updateFilters(){ + var doBC = Utils.getGlobalBCInput(); + if (doBC) { + // stages that we want to apply filters to... + var fStages = [ + groundtruthMapStage, baseImageStage, + spotContentStage, probeLayoutStage, + layoutSampledStage + ]; + + // apply the filters + const brightness = Utils.getBrightnessInput(); + const contrast = Utils.getContrastInput(); + for (let i = 0; i < fStages.length; i++) { + const fStage = fStages[i]; + let image = Utils.getFirstImageFromStage(fStage); + Utils.applyBrightnessContrast(image, brightness, contrast); + } + + // for the resulting images, the sampling function, Utils.ComputeProbeValue_gs(), + // is made B/C aware and using Konva's built-in filters directly. + } + + // call global visual update + doUpdate(); + } + // update filters once immediately + updateFilters(); + G_UpdateFilters = updateFilters; + doUpdate(); Utils.updateAdvancedMode(); diff --git a/app/src/utils.js b/app/src/utils.js index 46de8a3..6b23fa3 100644 --- a/app/src/utils.js +++ b/app/src/utils.js @@ -143,6 +143,9 @@ const Utils = { getRowsInput: function(){ return G_GUI_Controller.pixelCountY; }, getColsInput: function(){ return G_GUI_Controller.pixelCountX; }, + getBrightnessInput: function(){ return G_GUI_Controller.brightness; }, + getContrastInput: function(){ return G_GUI_Controller.contrast; }, + getGlobalBCInput: function(){ return G_GUI_Controller.globalBC; }, getCellWInput: function(){ return this.getInputValueInt($('#iCellW')); }, getCellHInput: function(){ return this.getInputValueInt($('#iCellH')); }, getSpotXInput: function(){ return this.getInputValueInt($('#iSpotX')); }, @@ -1504,6 +1507,17 @@ const Utils = { // grab the pixel data from the pixel selection area var pxData = ctx.getImageData(0,0,cv.width,cv.height); + // hack to directly use Konva's built-in filters code + if (typeof Konva != 'undefined') { + var brightnessFunc = Konva.Filters.Brighten.bind({ + brightness: () => this.getBrightnessInput()}); + var contrastFunc = Konva.Filters.Contrast.bind({ + contrast: () => this.getContrastInput()}); + // apply it directly to out image data before we sample it. + brightnessFunc(pxData); + contrastFunc(pxData); + } + // compute the average pixel (excluding 0-0-0-0 rgba pixels) var pxColor = this.get_avg_pixel_gs(pxData); @@ -1515,6 +1529,43 @@ const Utils = { return pxColor; }, + /** + * Applies Brightness/Contrast (B/C) values to a given Konva stage or drawable. + * @param {*} drawable The Konva stage or drawable / drawElement. + * @param {*} brightness The brightness value, from -1 to 1. + * @param {*} contrast The contrast value, mainly from -100 to 100. + */ + applyBrightnessContrast: function(drawable, brightness=0, contrast=0) { + // cache step is need for filter effects to be visible. + // https://konvajs.org/docs/performance/Shape_Caching.html + drawable.cache(); + + // Filters: https://konvajs.org/api/Konva.Filters.html + // Brightness => https://konvajs.org/docs/filters/Brighten.html + // Contrast => https://konvajs.org/docs/filters/Contrast.html + var currentFilters = drawable.filters(); + // null check, default to empty array if n/a. + currentFilters = currentFilters != null ? currentFilters : []; + // Add filter if not already included... + var currentFiltersByName = currentFilters.map(x => x.name); + var filtersToSet = currentFilters; + var added = 0; + ['Brighten', 'Contrast'].forEach(filterName => { + if (!currentFiltersByName.includes(filterName)) { + filtersToSet.push(Konva.Filters[filterName]); + added++; + } + }); + drawable.filters(filtersToSet); + if (G_DEBUG) { + console.log("filters added:", added); + } + + // apply B/C filter values + drawable.brightness(brightness); + drawable.contrast(contrast); + }, + /** * Find and gets the first "image" type from the first layer of the given Konva stage * @param {*} stage the stage to search through