|
@@ -5,26 +5,28 @@
|
|
|
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
|
|
*/
|
|
|
|
|
|
-import { PluginContext } from '../context';
|
|
|
+import { CameraHelperParams } from '../../mol-canvas3d/helper/camera-helper';
|
|
|
import { ImagePass } from '../../mol-canvas3d/passes/image';
|
|
|
-import { StateSelection } from '../../mol-state';
|
|
|
-import { PluginStateObject } from '../../mol-plugin-state/objects';
|
|
|
-import { Task, RuntimeContext } from '../../mol-task';
|
|
|
import { canvasToBlob } from '../../mol-canvas3d/util';
|
|
|
+import { PluginComponent } from '../../mol-plugin-state/component';
|
|
|
+import { PluginStateObject } from '../../mol-plugin-state/objects';
|
|
|
+import { StateSelection } from '../../mol-state';
|
|
|
+import { RuntimeContext, Task } from '../../mol-task';
|
|
|
import { download } from '../../mol-util/download';
|
|
|
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
|
|
-import { SyncRuntimeContext } from '../../mol-task/execution/synchronous';
|
|
|
-import { CameraHelperParams, CameraHelperProps } from '../../mol-canvas3d/helper/camera-helper';
|
|
|
import { SetUtils } from '../../mol-util/set';
|
|
|
+import { PluginContext } from '../context';
|
|
|
|
|
|
-export { ViewportScreenshotHelper };
|
|
|
+export { ViewportScreenshotHelper, ViewportScreenshotHelperParams };
|
|
|
|
|
|
namespace ViewportScreenshotHelper {
|
|
|
export type ResolutionSettings = PD.Values<ReturnType<ViewportScreenshotHelper['createParams']>>['resolution']
|
|
|
export type ResolutionTypes = ResolutionSettings['name']
|
|
|
}
|
|
|
|
|
|
-class ViewportScreenshotHelper {
|
|
|
+type ViewportScreenshotHelperParams = PD.Values<ReturnType<ViewportScreenshotHelper['createParams']>>
|
|
|
+
|
|
|
+class ViewportScreenshotHelper extends PluginComponent {
|
|
|
private createParams() {
|
|
|
const max = Math.min(this.plugin.canvas3d ? this.plugin.canvas3d.webgl.maxRenderbufferSize : 4096, 4096);
|
|
|
return {
|
|
@@ -56,12 +58,16 @@ class ViewportScreenshotHelper {
|
|
|
return this._params = this.createParams();
|
|
|
}
|
|
|
|
|
|
+ readonly behaviors = {
|
|
|
+ values: this.ev.behavior<ViewportScreenshotHelperParams>({
|
|
|
+ transparent: this.params.transparent.defaultValue,
|
|
|
+ axes: { name: 'off', params: {} },
|
|
|
+ resolution: this.params.resolution.defaultValue
|
|
|
+ })
|
|
|
+ };
|
|
|
+
|
|
|
get values() {
|
|
|
- return {
|
|
|
- transparent: this.transparent,
|
|
|
- axes: this.axes,
|
|
|
- resolution: this.currentResolution
|
|
|
- };
|
|
|
+ return this.behaviors.values.value;
|
|
|
}
|
|
|
|
|
|
private getCanvasSize() {
|
|
@@ -71,36 +77,38 @@ class ViewportScreenshotHelper {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- transparent = this.params.transparent.defaultValue
|
|
|
- axes: CameraHelperProps['axes'] = { name: 'off', params: {} }
|
|
|
- currentResolution = this.params.resolution.defaultValue
|
|
|
-
|
|
|
private getSize() {
|
|
|
- switch (this.currentResolution.name ) {
|
|
|
+ const values = this.values;
|
|
|
+ switch (values.resolution.name ) {
|
|
|
case 'viewport': return this.getCanvasSize();
|
|
|
case 'hd': return { width: 1280, height: 720 };
|
|
|
case 'full-hd': return { width: 1920, height: 1080 };
|
|
|
case 'ultra-hd': return { width: 3840, height: 2160 };
|
|
|
- default: return { width: this.currentResolution.params.width, height: this.currentResolution.params.height };
|
|
|
+ default: return { width: values.resolution.params.width, height: values.resolution.params.height };
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private _imagePass: ImagePass;
|
|
|
-
|
|
|
- get imagePass() {
|
|
|
- if (this._imagePass) return this._imagePass;
|
|
|
-
|
|
|
+ private createPass(mutlisample: boolean) {
|
|
|
const c = this.plugin.canvas3d!;
|
|
|
- this._imagePass = c.getImagePass({
|
|
|
- transparentBackground: this.transparent,
|
|
|
- cameraHelper: { axes: this.axes },
|
|
|
+ return this.plugin.canvas3d!.getImagePass({
|
|
|
+ transparentBackground: this.values.transparent,
|
|
|
+ cameraHelper: { axes: this.values.axes },
|
|
|
multiSample: {
|
|
|
- mode: 'on',
|
|
|
+ mode: mutlisample ? 'on' : 'off',
|
|
|
sampleLevel: c.webgl.extensions.colorBufferFloat ? 4 : 2
|
|
|
},
|
|
|
postprocessing: c.props.postprocessing
|
|
|
});
|
|
|
- return this._imagePass;
|
|
|
+ }
|
|
|
+
|
|
|
+ private _previewPass: ImagePass;
|
|
|
+ private get previewPass() {
|
|
|
+ return this._previewPass || (this._previewPass = this.createPass(false));
|
|
|
+ }
|
|
|
+
|
|
|
+ private _imagePass: ImagePass;
|
|
|
+ get imagePass() {
|
|
|
+ return this._imagePass || (this._imagePass = this.createPass(true));
|
|
|
}
|
|
|
|
|
|
getFilename() {
|
|
@@ -116,27 +124,41 @@ class ViewportScreenshotHelper {
|
|
|
return canvas;
|
|
|
}();
|
|
|
|
|
|
- // private preview = () => {
|
|
|
- // const { width, height } = this.getSize()
|
|
|
- // if (width <= 0 || height <= 0) return
|
|
|
-
|
|
|
- // let w: number, h: number
|
|
|
- // const aH = maxHeightUi / height
|
|
|
- // const aW = maxWidthUi / width
|
|
|
- // if (aH < aW) {
|
|
|
- // h = Math.round(Math.min(maxHeightUi, height))
|
|
|
- // w = Math.round(width * (h / height))
|
|
|
- // } else {
|
|
|
- // w = Math.round(Math.min(maxWidthUi, width))
|
|
|
- // h = Math.round(height * (w / width))
|
|
|
- // }
|
|
|
- // setCanvasSize(this.canvas, w, h)
|
|
|
- // const pixelRatio = this.plugin.canvas3d?.webgl.pixelRatio || 1
|
|
|
- // const pw = Math.round(w * pixelRatio)
|
|
|
- // const ph = Math.round(h * pixelRatio)
|
|
|
- // const imageData = this.imagePass.getImageData(pw, ph)
|
|
|
- // this.canvasContext.putImageData(imageData, 0, 0)
|
|
|
- // }
|
|
|
+ private previewCanvas = function () {
|
|
|
+ const canvas = document.createElement('canvas');
|
|
|
+ return canvas;
|
|
|
+ }();
|
|
|
+
|
|
|
+ getPreview(maxDim = 640) {
|
|
|
+ const { width, height } = this.getSize();
|
|
|
+ if (width <= 0 || height <= 0) return;
|
|
|
+
|
|
|
+ const f = width / height;
|
|
|
+
|
|
|
+ let w = 0, h = 0;
|
|
|
+ if (f > 1) {
|
|
|
+ w = maxDim;
|
|
|
+ h = Math.round(maxDim / f);
|
|
|
+ } else {
|
|
|
+ h = maxDim;
|
|
|
+ w = Math.round(maxDim * f);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.previewPass.setProps({
|
|
|
+ cameraHelper: { axes: this.values.axes },
|
|
|
+ transparentBackground: this.values.transparent,
|
|
|
+ // TODO: optimize because this creates a copy of a large object!
|
|
|
+ postprocessing: this.plugin.canvas3d!.props.postprocessing
|
|
|
+ });
|
|
|
+ const imageData = this.previewPass.getImageData(w, h);
|
|
|
+ const canvas = this.previewCanvas;
|
|
|
+ canvas.width = imageData.width;
|
|
|
+ canvas.height = imageData.height;
|
|
|
+ const canvasCtx = canvas.getContext('2d');
|
|
|
+ if (!canvasCtx) throw new Error('Could not create canvas 2d context');
|
|
|
+ canvasCtx.putImageData(imageData, 0, 0);
|
|
|
+ return { canvas, width: w, height: h };
|
|
|
+ }
|
|
|
|
|
|
private async draw(ctx: RuntimeContext) {
|
|
|
const { width, height } = this.getSize();
|
|
@@ -144,9 +166,10 @@ class ViewportScreenshotHelper {
|
|
|
|
|
|
await ctx.update('Rendering image...');
|
|
|
this.imagePass.setProps({
|
|
|
- cameraHelper: { axes: this.axes },
|
|
|
- transparentBackground: this.transparent,
|
|
|
- postprocessing: this.plugin.canvas3d!.props.postprocessing // TODO this line should not be required, updating should work by listening to this.plugin.events.canvas3d.settingsUpdated
|
|
|
+ cameraHelper: { axes: this.values.axes },
|
|
|
+ transparentBackground: this.values.transparent,
|
|
|
+ // TODO: optimize because this creates a copy of a large object!
|
|
|
+ postprocessing: this.plugin.canvas3d!.props.postprocessing
|
|
|
});
|
|
|
const imageData = this.imagePass.getImageData(width, height);
|
|
|
|
|
@@ -160,7 +183,7 @@ class ViewportScreenshotHelper {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- async copyToClipboard() {
|
|
|
+ private copyToClipboardTask() {
|
|
|
const cb = navigator.clipboard as any;
|
|
|
|
|
|
if (!cb.write) {
|
|
@@ -168,41 +191,37 @@ class ViewportScreenshotHelper {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const blob = await canvasToBlob(this.canvas, 'png');
|
|
|
- const item = new ClipboardItem({ 'image/png': blob });
|
|
|
- cb.write([item]);
|
|
|
+ return Task.create('Copy Image', async ctx => {
|
|
|
+ await this.draw(ctx);
|
|
|
+ await ctx.update('Downloading image...');
|
|
|
+ const blob = await canvasToBlob(this.canvas, 'png');
|
|
|
+ const item = new ClipboardItem({ 'image/png': blob });
|
|
|
+ cb.write([item]);
|
|
|
+ this.plugin.log.message('Image copied to clipboard.');
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- this.plugin.log.message('Image copied to clipboard.');
|
|
|
+ copyToClipboard() {
|
|
|
+ const task = this.copyToClipboardTask();
|
|
|
+ if (!task) return;
|
|
|
+ return this.plugin.runTask(task);
|
|
|
}
|
|
|
|
|
|
private downloadTask() {
|
|
|
return Task.create('Download Image', async ctx => {
|
|
|
- this.draw(ctx);
|
|
|
+ await this.draw(ctx);
|
|
|
await ctx.update('Downloading image...');
|
|
|
const blob = await canvasToBlob(this.canvas, 'png');
|
|
|
download(blob, this.getFilename());
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- downloadCurrent() {
|
|
|
- return this.plugin.runTask(Task.create('Download Image', async ctx => {
|
|
|
- await ctx.update('Downloading image...');
|
|
|
- const blob = await canvasToBlob(this.canvas, 'png');
|
|
|
- download(blob, this.getFilename());
|
|
|
- }));
|
|
|
- }
|
|
|
-
|
|
|
- async imageData() {
|
|
|
- await this.draw(SyncRuntimeContext);
|
|
|
- return this.canvas.toDataURL();
|
|
|
- }
|
|
|
-
|
|
|
download() {
|
|
|
this.plugin.runTask(this.downloadTask());
|
|
|
}
|
|
|
|
|
|
constructor(private plugin: PluginContext) {
|
|
|
-
|
|
|
+ super();
|
|
|
}
|
|
|
}
|
|
|
|