Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/high resolution screenshot #37

Open
wants to merge 115 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
5bcb1ec
feat: simple screenshot button in settings
seankmartin Jul 12, 2024
5f296e8
poc: scalable screenshot
seankmartin Jul 12, 2024
c425f54
Merge branch 'master' into feature/high-resolution-screenshot
seankmartin Jul 29, 2024
a111495
feat(ui): add camera icon to top bar
seankmartin Aug 27, 2024
71b4ec7
refactor: pull viewer complexity for screenshot into new class
seankmartin Aug 27, 2024
0472c12
refactor: clarify variable
seankmartin Aug 27, 2024
e2b15b9
refactor: move interface for screenshot to utils
seankmartin Aug 28, 2024
504228a
fix: revert help menu change for temp testing
seankmartin Aug 28, 2024
e0b1504
feat(ui): add screenshot UI elements
seankmartin Aug 28, 2024
bd52bd7
fix: allow forcing screenshot if viewer not ready
seankmartin Aug 28, 2024
58deb88
feat: crop screenshot to view panels
seankmartin Aug 28, 2024
4ef21f4
refactor: clean up screenshot code
seankmartin Aug 28, 2024
ac9fe69
refactor: remove atob and btoa to reduce complexity of codebase
seankmartin Aug 28, 2024
0c219d3
fix(ui): remove SVG titles in buttons
seankmartin Aug 28, 2024
eac9be3
fix(ui): better icon fix
seankmartin Aug 29, 2024
17c8a88
feat: switch between buttons for save or force screenshot
seankmartin Aug 29, 2024
d651e55
refactor: remove event listener complexity from viewer
seankmartin Aug 29, 2024
570650a
feat: first version of screenshot statistics
seankmartin Aug 30, 2024
aed271e
feat: auto force screenshot if no updates
seankmartin Aug 30, 2024
3b87bee
refactor: simplify logic for object interaction
seankmartin Aug 30, 2024
e39740a
refactor: clean up screenshot menu
seankmartin Sep 2, 2024
3d2804b
feat: add minimal CSS styling for screenshot
seankmartin Sep 2, 2024
53b02e5
feat(ui): only show stats while in progress
seankmartin Sep 2, 2024
51e01c3
feat(ui): improve screenshot stats
seankmartin Sep 2, 2024
24b04a9
feat: hide screenshot controls when not in use and update stats with …
seankmartin Sep 3, 2024
d11dff2
feat(ui): improve table updating
seankmartin Sep 3, 2024
6bafc30
refactor: improve screenshot from viewer code
seankmartin Sep 3, 2024
81154b6
refactor: small rename for consistency
seankmartin Sep 3, 2024
aa4b419
feat: include state log for screenshot replication
seankmartin Sep 3, 2024
2d59788
refactor: change trackable screenshot from type to class
seankmartin Sep 3, 2024
f40c432
refactor: clarify force check for bool
seankmartin Sep 3, 2024
d666a3e
remove: is DataPanel flag in favour of checking instance
seankmartin Sep 3, 2024
7fbbf88
feat(ui): small UI improvements in screenshot menu
seankmartin Sep 3, 2024
c6dca6c
refactor: rename classes
seankmartin Sep 3, 2024
e0facf0
refactor: clarify interaction between screenshot objects
seankmartin Sep 3, 2024
f928d07
refactor: make viewer menu responsible for manipulating data from con…
seankmartin Sep 3, 2024
cf44402
refactor: rename screenshot manager file
seankmartin Sep 16, 2024
4fc91b8
feat: lock screenshot menu until cancelled or done
seankmartin Sep 16, 2024
a2d504b
feat: close tool menus before opening JSON state editor or screenshot…
seankmartin Sep 16, 2024
7f61396
feat: show indication of screenshot size, warning if big, don't hide …
seankmartin Sep 16, 2024
15bf297
feat: progress on image (slice and volume) res stats
seankmartin Sep 16, 2024
3e2afd8
feat: resolution from each layer type
seankmartin Sep 17, 2024
5d1a0ee
feat: add per panel resolution indicator
seankmartin Sep 17, 2024
7d2930b
feat: separate screenshot cancel button
seankmartin Sep 17, 2024
5f84eb6
feat: separate close button
seankmartin Sep 17, 2024
beeb355
feat(ui): show stats on menu even when screenshot not running
seankmartin Sep 17, 2024
949bcdf
feat: update resolution rounding for slices > 1px
seankmartin Sep 17, 2024
042ee62
feat: populate layer stats in menu as they laod
seankmartin Sep 19, 2024
b62fe06
feat: hide non-visible stats
seankmartin Sep 19, 2024
3289c2c
feat: reduce screenshot hang time
seankmartin Sep 19, 2024
bd91904
feat: improve stats display and checking for hanging screenshots
seankmartin Sep 20, 2024
bb07ae8
feat: detect ortographic view stats
seankmartin Sep 23, 2024
54a20d2
fix(ui): don't round resolution < 1 in display
seankmartin Sep 23, 2024
4230a4c
docs: note about why use debug close menu
seankmartin Sep 23, 2024
7f0d199
revert: don't grab JSON state with screenshot
seankmartin Sep 26, 2024
beab5f4
feat: include voxel resolution in screenshot
seankmartin Sep 26, 2024
681d923
feat: allow fixed 2D panel FOV in screenshots with checkbox
seankmartin Sep 26, 2024
7e5dda2
fix: formatting
seankmartin Sep 26, 2024
5862bf9
fix: possible race condition between screenshot taking and resetting …
seankmartin Sep 26, 2024
7ebf558
feat: add panel pixel sizes
seankmartin Sep 26, 2024
7e9404f
refactor: small changes for clarity
seankmartin Sep 27, 2024
461a79e
docs: add docstring for resolution functions
seankmartin Sep 27, 2024
970a3f7
docs, refactor: improve screenshot clarity
seankmartin Sep 27, 2024
0cb7ef2
docs: screenshot manager notes
seankmartin Sep 27, 2024
6c171df
feat: combine panel physical and pixel resolution in UI
seankmartin Sep 27, 2024
9b4c306
fix: handle correclty the pixel indicator on zoom in UI
seankmartin Sep 27, 2024
c9eb62a
feat(ui): initial version of tooltips
seankmartin Sep 27, 2024
ce618d1
#NA-356 Screenshot Dialog UI updates
vidhya-metacell Oct 10, 2024
937a553
#NA-356 Screenshot Modal UI
vidhya-metacell Oct 11, 2024
d1457d9
#NA-356 PR comments update
vidhya-metacell Oct 17, 2024
12ad19d
#NA-356 PR updates
vidhya-metacell Oct 18, 2024
71bb1a8
#NA-356 Screenshot UI pr updates
vidhya-metacell Oct 21, 2024
f045680
feat: flip pixel and physical resolution
seankmartin Oct 28, 2024
b992557
chore: run fix formatting command for merge
seankmartin Oct 28, 2024
9a72905
fix: apply styling to children of screenshot menu
seankmartin Oct 28, 2024
302c119
Revert "fix: apply styling to children of screenshot menu"
seankmartin Oct 28, 2024
28bfb99
refactor: remove all svg files in local folder
seankmartin Oct 28, 2024
f8f39df
fix: help close positioning after using ikonate
seankmartin Oct 28, 2024
9647bd9
fix: apply styling to children of screenshot menu
seankmartin Oct 28, 2024
460d4d7
Revert "fix: apply styling to children of screenshot menu"
seankmartin Oct 28, 2024
c561ea8
fix: remove left right padding after ikonate usage on close button
seankmartin Oct 28, 2024
e03af09
refactor: combine same selector into one
seankmartin Oct 28, 2024
836fceb
fix: remove border on panel resolution table
seankmartin Oct 28, 2024
3441be2
fix: Panel type text not offset
seankmartin Oct 28, 2024
10e5548
Revert "fix: apply styling to children of screenshot menu"
seankmartin Oct 28, 2024
aba9956
Reapply "fix: apply styling to children of screenshot menu"
seankmartin Oct 28, 2024
d711bcf
feat: move body content to new div to help scroll feat
seankmartin Oct 29, 2024
e30b07d
feat: color dimension in screenshot
seankmartin Oct 29, 2024
50e240f
refactor: rename screenshot button
seankmartin Oct 29, 2024
4515da1
feat: add copy to clipboard resolution function
seankmartin Oct 29, 2024
024be79
fix: disable UI elements during screenshot
seankmartin Oct 29, 2024
f0efea9
feat: show forcing as status message
seankmartin Oct 29, 2024
5a1f605
fix: correct user screenshot force handling
seankmartin Oct 29, 2024
dce43eb
feat(python): support resolution scale factor in CLI
seankmartin Oct 29, 2024
045d9cf
NA-362-screenshot scrollbar change style
Aiga115 Nov 1, 2024
e0d81bf
NA-362-screenshot fix tooltip ui and table ui
Aiga115 Nov 5, 2024
92af1f2
feat: small changes and revert scroll bar styling
seankmartin Nov 6, 2024
4b9b04e
Merge pull request #46 from MetaCell/feature/NA-362-screenshot
seankmartin Nov 6, 2024
cde708e
Merge pull request #44 from MetaCell/feature/screenshot-cli-scale
seankmartin Nov 6, 2024
e952d59
Merge branch 'feature/NA-362' into feature/NA-356
seankmartin Nov 6, 2024
34672e1
feat: use :is in css to restrict changes
seankmartin Nov 6, 2024
532f46f
Merge pull request #40 from MetaCell/feature/NA-356
seankmartin Nov 6, 2024
2be0ebb
feat(ui): small screenshot updates
seankmartin Nov 6, 2024
b2e4509
refactor: clearer separation of logic for resolution copy to table
seankmartin Nov 7, 2024
b3b8da7
fix: correct logic for flipping between fixed FOV and not
seankmartin Nov 8, 2024
eefbbc5
feat: improve resolution to clipboard copy
seankmartin Nov 8, 2024
33e3c3b
refactor: only call setup tooltips once
seankmartin Nov 11, 2024
940b8d6
feat: add layer tooltip
seankmartin Nov 11, 2024
f7edfef
refactor: move tooltip strings to const
seankmartin Nov 11, 2024
c4c5da9
fix: handle non-integer resolutions and clean up handling
seankmartin Nov 11, 2024
bf3e619
refactor: clean up css defs and formatting
seankmartin Nov 11, 2024
a415c0f
refactor: removed unused VR variables
seankmartin Nov 11, 2024
ca40e51
fix: remove accidental log
seankmartin Nov 11, 2024
2266dc3
Merge branch 'master' into feature/high-resolution-screenshot
seankmartin Nov 11, 2024
5bcfd9e
Merge branch 'master' into feature/high-resolution-screenshot
seankmartin Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/display_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import { FramerateMonitor } from "#src/util/framerate.js";
import type { mat4 } from "#src/util/geom.js";
import { parseFixedLengthArray, verifyFloat01 } from "#src/util/json.js";
import { NullarySignal } from "#src/util/signal.js";
import type { TrackableScreenshotModeValue } from "#src/util/trackable_screenshot_mode.js";
import {
ScreenshotModes,
trackableScreenshotModeValue,
} from "#src/util/trackable_screenshot_mode.js";
import type { WatchableVisibilityPriority } from "#src/visibility_priority/frontend.js";
import type { GL } from "#src/webgl/context.js";
import { initializeWebGL } from "#src/webgl/context.js";
Expand Down Expand Up @@ -221,8 +226,15 @@ export abstract class RenderedPanel extends RefCounted {
0,
clippedBottom - clippedTop,
));
viewport.logicalWidth = logicalWidth;
viewport.logicalHeight = logicalHeight;
if (this.context.screenshotMode.value !== ScreenshotModes.OFF) {
viewport.width = logicalWidth * screenToCanvasPixelScaleX;
viewport.height = logicalHeight * screenToCanvasPixelScaleY;
viewport.logicalWidth = logicalWidth * screenToCanvasPixelScaleX;
viewport.logicalHeight = logicalHeight * screenToCanvasPixelScaleY;
} else {
viewport.logicalWidth = logicalWidth;
viewport.logicalHeight = logicalHeight;
}
viewport.visibleLeftFraction = (clippedLeft - logicalLeft) / logicalWidth;
viewport.visibleTopFraction = (clippedTop - logicalTop) / logicalHeight;
viewport.visibleWidthFraction = clippedWidth / logicalWidth;
Expand Down Expand Up @@ -300,6 +312,10 @@ export abstract class RenderedPanel extends RefCounted {
return true;
}

get isDataPanel() {
return false;
}

// Returns a number that determine the order in which panels are drawn. This is used by CdfPanel
// to ensure it is drawn after other panels that update the histogram.
//
Expand Down Expand Up @@ -403,6 +419,7 @@ export class DisplayContext extends RefCounted implements FrameNumberCounter {
rootRect: DOMRect | undefined;
resizeGeneration = 0;
boundsGeneration = -1;
screenshotMode: TrackableScreenshotModeValue = trackableScreenshotModeValue();
private framerateMonitor = new FramerateMonitor();

private continuousCameraMotionInProgress = false;
Expand Down Expand Up @@ -575,8 +592,10 @@ export class DisplayContext extends RefCounted implements FrameNumberCounter {
const { resizeGeneration } = this;
if (this.boundsGeneration === resizeGeneration) return;
const { canvas } = this;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
if (this.screenshotMode.value === ScreenshotModes.OFF) {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
}
this.canvasRect = canvas.getBoundingClientRect();
this.rootRect = this.container.getBoundingClientRect();
this.boundsGeneration = resizeGeneration;
Expand Down
4 changes: 4 additions & 0 deletions src/perspective_view/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ export class PerspectivePanel extends RenderedDataPanel {
);
}

get isDataPanel() {
return true;
}

/**
* If boolean value is true, sliceView is shown unconditionally, regardless of the value of
* this.viewer.showSliceViews.value.
Expand Down
7 changes: 5 additions & 2 deletions src/python_integration/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { convertEndian32, Endianness } from "#src/util/endian.js";
import { verifyOptionalString } from "#src/util/json.js";
import { Signal } from "#src/util/signal.js";
import { getCachedJson } from "#src/util/trackable.js";
import { ScreenshotModes } from "#src/util/trackable_screenshot_mode.js";
import type { Viewer } from "#src/viewer.js";

export class ScreenshotHandler extends RefCounted {
Expand Down Expand Up @@ -124,12 +125,14 @@ export class ScreenshotHandler extends RefCounted {
return;
}
const { viewer } = this;
if (!viewer.isReady()) {
const forceScreenshot =
this.viewer.display.screenshotMode.value === ScreenshotModes.FORCE;
if (!viewer.isReady() && !forceScreenshot) {
this.wasAlreadyVisible = false;
this.throttledSendStatistics(requestState);
return;
}
if (!this.wasAlreadyVisible) {
if (!this.wasAlreadyVisible && !forceScreenshot) {
this.throttledSendStatistics(requestState);
this.wasAlreadyVisible = true;
this.debouncedMaybeSendScreenshot();
Expand Down
3 changes: 3 additions & 0 deletions src/sliceview/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ export class SliceViewPanel extends RenderedDataPanel {
get rpcId() {
return this.sliceView.rpcId!;
}
get isDataPanel() {
return true;
}

private offscreenFramebuffer = this.registerDisposer(
new FramebufferConfiguration(this.gl, {
Expand Down
24 changes: 24 additions & 0 deletions src/ui/screenshot_menu.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @license
* Copyright 2018 Google Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

.neuroglancer-screenshot-dialog {
width: 80%;
}

.close-button {
position: absolute;
right: 15px;
}
206 changes: 206 additions & 0 deletions src/ui/screenshot_menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/**
* @license
* Copyright 2024 Google Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { debounce } from "lodash-es";
import { Overlay } from "#src/overlay.js";
import "#src/ui/screenshot_menu.css";

import type { StatisticsActionState } from "#src/util/screenshot.js";

import { ScreenshotModes } from "#src/util/trackable_screenshot_mode.js";
import type { Viewer } from "#src/viewer.js";

export class ScreenshotDialog extends Overlay {
private nameInput: HTMLInputElement;
private saveButton: HTMLButtonElement;
private closeButton: HTMLButtonElement;
private forceScreenshotButton: HTMLButtonElement;
private statisticsTable: HTMLTableElement;
private titleBar: HTMLDivElement;
private screenshotMode: ScreenshotModes;
constructor(public viewer: Viewer) {
super();

this.content.classList.add("neuroglancer-screenshot-dialog");
this.screenshotMode = this.viewer.display.screenshotMode.value;

this.content.appendChild(this.createCloseButton());
this.content.appendChild(this.createScaleRadioButtons());
this.content.appendChild(this.createNameInput());
this.content.appendChild(this.createSaveAndForceScreenshotButtons());
this.content.appendChild(this.createStatisticsTable());

this.registerDisposer(
this.viewer.screenshotActionHandler.sendScreenshotRequested.add(() => {
this.debouncedShowSaveOrForceScreenshotButton();
this.dispose();
}),
);
this.registerDisposer(
this.viewer.screenshotActionHandler.sendStatisticsRequested.add(
(actionState) => {
this.populateStatistics(actionState);
},
),
);
this.closeButton;
}

private createSaveAndForceScreenshotButtons() {
this.createSaveButton();
this.createForceScreenshotButton();

return this.screenshotMode === ScreenshotModes.OFF
? this.saveButton
: this.forceScreenshotButton;
}

private createCloseButton() {
const closeButton = (this.closeButton = document.createElement("button"));
closeButton.classList.add("close-button");
closeButton.textContent = "Close";
closeButton.addEventListener("click", () => this.dispose());
return closeButton;
}

private createNameInput() {
const nameInput = (this.nameInput = document.createElement("input"));
nameInput.type = "text";
nameInput.placeholder = "Enter filename...";
seankmartin marked this conversation as resolved.
Show resolved Hide resolved
return nameInput;
}

private createSaveButton() {
const saveButton = (this.saveButton = document.createElement("button"));
saveButton.textContent = "Take screenshot";
saveButton.title =
"Take a screenshot of the current view and save it to a png file";
saveButton.addEventListener("click", () => {
this.screenshot();
});
return saveButton;
}

private createForceScreenshotButton() {
const forceScreenshotButton = (this.forceScreenshotButton =
document.createElement("button"));
forceScreenshotButton.textContent = "Force screenshot";
forceScreenshotButton.title =
"Force a screenshot of the current view and save it to a png file";
seankmartin marked this conversation as resolved.
Show resolved Hide resolved
forceScreenshotButton.addEventListener("click", () => {
this.forceScreenshot();
});
return forceScreenshotButton;
}

private createScaleRadioButtons() {
const scaleRadioButtons = document.createElement("div");
scaleRadioButtons.classList.add("scale-radio-buttons");
const scales = [1, 2, 4];
for (const scale of scales) {
const label = document.createElement("label");
const input = document.createElement("input");
input.type = "radio";
input.name = "screenshot-scale";
input.value = scale.toString();
input.checked = scale === this.screenshotHandler.screenshotScale;
label.appendChild(input);
label.appendChild(document.createTextNode(`Scale ${scale}x`));
scaleRadioButtons.appendChild(label);
input.addEventListener("change", () => {
this.screenshotHandler.screenshotScale = scale;
});
}
return scaleRadioButtons;
}

private createStatisticsTable() {
const titleBar = document.createElement("div");
this.titleBar = titleBar;
titleBar.classList.add("neuroglancer-screenshot-statistics-title");
this.content.appendChild(titleBar);
this.statisticsTable = document.createElement("table");
this.statisticsTable.classList.add(
"neuroglancer-screenshot-statistics-table",
);
this.statisticsTable.createTHead().insertRow().innerHTML =
seankmartin marked this conversation as resolved.
Show resolved Hide resolved
"<th>Key</th><th>Value</th>";
this.statisticsTable.title = "Screenshot statistics";

this.setTitleBarText();
this.populateStatistics(undefined);
return titleBar;
}

private setTitleBarText() {
const titleBarText =
this.screenshotMode !== ScreenshotModes.OFF
? "Screenshot in progress with the following statistics:"
: "Start screenshot mode to see statistics";
this.titleBar.textContent = titleBarText;
this.titleBar.appendChild(this.statisticsTable);
}

private forceScreenshot() {
this.screenshotHandler.forceScreenshot();
this.debouncedShowSaveOrForceScreenshotButton();
}

private screenshot() {
const filename = this.nameInput.value;
this.screenshotHandler.screenshot(filename);
this.debouncedShowSaveOrForceScreenshotButton();
}

private populateStatistics(actionState: StatisticsActionState | undefined) {
if (actionState !== undefined) {
while (this.statisticsTable.rows.length > 1) {
this.statisticsTable.deleteRow(1);
}
}
const statsRow = this.screenshotHandler.parseStatistics(actionState);

for (const key in statsRow) {
const row = this.statisticsTable.insertRow();
const keyCell = row.insertCell();
keyCell.textContent = key;
const valueCell = row.insertCell();
valueCell.textContent = String(statsRow[key as keyof typeof statsRow]);
}
}

private debouncedShowSaveOrForceScreenshotButton = debounce(() => {
this.showSaveOrForceScreenshotButton();
this.setTitleBarText();
}, 200);

private showSaveOrForceScreenshotButton() {
// Check to see if the global state matches the current state of the dialog
if (this.viewer.display.screenshotMode.value === this.screenshotMode) {
return;
}
if (this.viewer.display.screenshotMode.value === ScreenshotModes.OFF) {
this.content.replaceChild(this.saveButton, this.forceScreenshotButton);
} else {
this.content.replaceChild(this.forceScreenshotButton, this.saveButton);
}
this.screenshotMode = this.viewer.display.screenshotMode.value;
}

get screenshotHandler() {
return this.viewer.screenshotHandler;
}
}
Loading
Loading