molstar
Version:
A comprehensive macromolecular library.
413 lines • 19.2 kB
JavaScript
"use strict";
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ViewportScreenshotHelper = void 0;
var tslib_1 = require("tslib");
var camera_helper_1 = require("../../mol-canvas3d/helper/camera-helper");
var util_1 = require("../../mol-canvas3d/util");
var common_1 = require("../../mol-math/linear-algebra/3d/common");
var component_1 = require("../../mol-plugin-state/component");
var objects_1 = require("../../mol-plugin-state/objects");
var mol_state_1 = require("../../mol-state");
var mol_task_1 = require("../../mol-task");
var color_1 = require("../../mol-util/color");
var download_1 = require("../../mol-util/download");
var param_definition_1 = require("../../mol-util/param-definition");
var set_1 = require("../../mol-util/set");
var ViewportScreenshotHelper = /** @class */ (function (_super) {
(0, tslib_1.__extends)(ViewportScreenshotHelper, _super);
function ViewportScreenshotHelper(plugin) {
var _this = _super.call(this) || this;
_this.plugin = plugin;
_this._params = void 0;
_this.behaviors = {
values: _this.ev.behavior({
transparent: _this.params.transparent.defaultValue,
axes: { name: 'off', params: {} },
resolution: _this.params.resolution.defaultValue
}),
cropParams: _this.ev.behavior({ auto: true, relativePadding: 0.1 }),
relativeCrop: _this.ev.behavior({ x: 0, y: 0, width: 1, height: 1 }),
};
_this.events = {
previewed: _this.ev()
};
_this.canvas = function () {
var canvas = document.createElement('canvas');
return canvas;
}();
_this.previewCanvas = function () {
var canvas = document.createElement('canvas');
return canvas;
}();
_this.previewData = {
image: { data: new Uint8ClampedArray(1), width: 1, height: 0 },
background: (0, color_1.Color)(0),
transparent: false
};
return _this;
}
ViewportScreenshotHelper.prototype.createParams = function () {
var max = Math.min(this.plugin.canvas3d ? this.plugin.canvas3d.webgl.maxRenderbufferSize : 4096, 4096);
return {
resolution: param_definition_1.ParamDefinition.MappedStatic('viewport', {
viewport: param_definition_1.ParamDefinition.Group({}),
hd: param_definition_1.ParamDefinition.Group({}),
'full-hd': param_definition_1.ParamDefinition.Group({}),
'ultra-hd': param_definition_1.ParamDefinition.Group({}),
custom: param_definition_1.ParamDefinition.Group({
width: param_definition_1.ParamDefinition.Numeric(1920, { min: 128, max: max, step: 1 }),
height: param_definition_1.ParamDefinition.Numeric(1080, { min: 128, max: max, step: 1 }),
}, { isFlat: true })
}, {
options: [
['viewport', 'Viewport'],
['hd', 'HD (1280 x 720)'],
['full-hd', 'Full HD (1920 x 1080)'],
['ultra-hd', 'Ultra HD (3840 x 2160)'],
['custom', 'Custom']
]
}),
transparent: param_definition_1.ParamDefinition.Boolean(false),
axes: camera_helper_1.CameraHelperParams.axes,
};
};
Object.defineProperty(ViewportScreenshotHelper.prototype, "params", {
get: function () {
if (this._params)
return this._params;
return this._params = this.createParams();
},
enumerable: false,
configurable: true
});
Object.defineProperty(ViewportScreenshotHelper.prototype, "values", {
get: function () {
return this.behaviors.values.value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(ViewportScreenshotHelper.prototype, "cropParams", {
get: function () {
return this.behaviors.cropParams.value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(ViewportScreenshotHelper.prototype, "relativeCrop", {
get: function () {
return this.behaviors.relativeCrop.value;
},
enumerable: false,
configurable: true
});
ViewportScreenshotHelper.prototype.getCanvasSize = function () {
var _a, _b;
return {
width: ((_a = this.plugin.canvas3d) === null || _a === void 0 ? void 0 : _a.webgl.gl.drawingBufferWidth) || 0,
height: ((_b = this.plugin.canvas3d) === null || _b === void 0 ? void 0 : _b.webgl.gl.drawingBufferHeight) || 0
};
};
ViewportScreenshotHelper.prototype.getSize = function () {
var 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: values.resolution.params.width, height: values.resolution.params.height };
}
};
ViewportScreenshotHelper.prototype.createPass = function (mutlisample) {
var c = this.plugin.canvas3d;
var _a = c.webgl.extensions, colorBufferFloat = _a.colorBufferFloat, textureFloat = _a.textureFloat;
var aoProps = c.props.postprocessing.occlusion;
return c.getImagePass({
transparentBackground: this.values.transparent,
cameraHelper: { axes: this.values.axes },
multiSample: {
mode: mutlisample ? 'on' : 'off',
sampleLevel: colorBufferFloat && textureFloat ? 4 : 2
},
postprocessing: (0, tslib_1.__assign)((0, tslib_1.__assign)({}, c.props.postprocessing), { occlusion: aoProps.name === 'on'
? { name: 'on', params: (0, tslib_1.__assign)((0, tslib_1.__assign)({}, aoProps.params), { samples: 128 }) }
: aoProps }),
marking: (0, tslib_1.__assign)({}, c.props.marking)
});
};
Object.defineProperty(ViewportScreenshotHelper.prototype, "previewPass", {
get: function () {
return this._previewPass || (this._previewPass = this.createPass(false));
},
enumerable: false,
configurable: true
});
Object.defineProperty(ViewportScreenshotHelper.prototype, "imagePass", {
get: function () {
if (this._imagePass) {
var c = this.plugin.canvas3d;
var aoProps = c.props.postprocessing.occlusion;
this._imagePass.setProps({
cameraHelper: { axes: this.values.axes },
transparentBackground: this.values.transparent,
// TODO: optimize because this creates a copy of a large object!
postprocessing: (0, tslib_1.__assign)((0, tslib_1.__assign)({}, c.props.postprocessing), { occlusion: aoProps.name === 'on'
? { name: 'on', params: (0, tslib_1.__assign)((0, tslib_1.__assign)({}, aoProps.params), { samples: 128 }) }
: aoProps }),
marking: (0, tslib_1.__assign)({}, c.props.marking)
});
return this._imagePass;
}
return this._imagePass = this.createPass(true);
},
enumerable: false,
configurable: true
});
ViewportScreenshotHelper.prototype.getFilename = function (extension) {
if (extension === void 0) { extension = '.png'; }
var models = this.plugin.state.data.select(mol_state_1.StateSelection.Generators.rootsOfType(objects_1.PluginStateObject.Molecule.Model)).map(function (s) { return s.obj.data; });
var uniqueIds = new Set();
models.forEach(function (m) { return uniqueIds.add(m.entryId.toUpperCase()); });
var idString = set_1.SetUtils.toArray(uniqueIds).join('-');
return "" + (idString || 'molstar-image') + extension;
};
ViewportScreenshotHelper.prototype.resetCrop = function () {
this.behaviors.relativeCrop.next({ x: 0, y: 0, width: 1, height: 1 });
};
ViewportScreenshotHelper.prototype.toggleAutocrop = function () {
if (this.cropParams.auto) {
this.behaviors.cropParams.next((0, tslib_1.__assign)((0, tslib_1.__assign)({}, this.cropParams), { auto: false }));
this.resetCrop();
}
else {
this.behaviors.cropParams.next((0, tslib_1.__assign)((0, tslib_1.__assign)({}, this.cropParams), { auto: true }));
}
};
Object.defineProperty(ViewportScreenshotHelper.prototype, "isFullFrame", {
get: function () {
var crop = this.relativeCrop;
return (0, common_1.equalEps)(crop.x, 0, 1e-5) && (0, common_1.equalEps)(crop.y, 0, 1e-5) && (0, common_1.equalEps)(crop.width, 1, 1e-5) && (0, common_1.equalEps)(crop.height, 1, 1e-5);
},
enumerable: false,
configurable: true
});
ViewportScreenshotHelper.prototype.autocrop = function (relativePadding) {
if (relativePadding === void 0) { relativePadding = this.cropParams.relativePadding; }
var _a = this.previewData.image, data = _a.data, width = _a.width, height = _a.height;
var isTransparent = this.previewData.transparent;
var bgColor = isTransparent ? this.previewData.background : 0xff000000 | this.previewData.background;
var l = width, r = 0, t = height, b = 0;
for (var j = 0; j < height; j++) {
var jj = j * width;
for (var i = 0; i < width; i++) {
var o = 4 * (jj + i);
if (isTransparent) {
if (data[o + 3] === 0)
continue;
}
else {
var c = (data[o] << 16) | (data[o + 1] << 8) | (data[o + 2]) | (data[o + 3] << 24);
if (c === bgColor)
continue;
}
if (i < l)
l = i;
if (i > r)
r = i;
if (j < t)
t = j;
if (j > b)
b = j;
}
}
if (l > r) {
var x = l;
l = r;
r = x;
}
if (t > b) {
var x = t;
t = b;
b = x;
}
var tw = r - l + 1, th = b - t + 1;
l -= relativePadding * tw;
r += relativePadding * tw;
t -= relativePadding * th;
b += relativePadding * th;
var crop = {
x: Math.max(0, l / width),
y: Math.max(0, t / height),
width: Math.min(1, (r - l + 1) / width),
height: Math.min(1, (b - t + 1) / height)
};
this.behaviors.relativeCrop.next(crop);
};
ViewportScreenshotHelper.prototype.getPreview = function (maxDim) {
if (maxDim === void 0) { maxDim = 320; }
var _a = this.getSize(), width = _a.width, height = _a.height;
if (width <= 0 || height <= 0)
return;
var f = width / height;
var w = 0, h = 0;
if (f > 1) {
w = maxDim;
h = Math.round(maxDim / f);
}
else {
h = maxDim;
w = Math.round(maxDim * f);
}
var canvasProps = this.plugin.canvas3d.props;
this.previewPass.setProps({
cameraHelper: { axes: this.values.axes },
transparentBackground: this.values.transparent,
// TODO: optimize because this creates a copy of a large object!
postprocessing: canvasProps.postprocessing,
marking: canvasProps.marking
});
var imageData = this.previewPass.getImageData(w, h);
var canvas = this.previewCanvas;
canvas.width = imageData.width;
canvas.height = imageData.height;
this.previewData.image = imageData;
this.previewData.background = canvasProps.renderer.backgroundColor;
this.previewData.transparent = this.values.transparent;
var canvasCtx = canvas.getContext('2d');
if (!canvasCtx)
throw new Error('Could not create canvas 2d context');
canvasCtx.putImageData(imageData, 0, 0);
if (this.cropParams.auto)
this.autocrop();
this.events.previewed.next(void 0);
return { canvas: canvas, width: w, height: h };
};
ViewportScreenshotHelper.prototype.getSizeAndViewport = function () {
var _a = this.getSize(), width = _a.width, height = _a.height;
var crop = this.relativeCrop;
var viewport = {
x: Math.floor(crop.x * width),
y: Math.floor(crop.y * height),
width: Math.ceil(crop.width * width),
height: Math.ceil(crop.height * height)
};
if (viewport.width + viewport.x > width)
viewport.width = width - viewport.x;
if (viewport.height + viewport.y > height)
viewport.height = height - viewport.y;
return { width: width, height: height, viewport: viewport };
};
ViewportScreenshotHelper.prototype.draw = function (ctx) {
return (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
var _a, width, height, viewport, imageData, canvas, canvasCtx;
return (0, tslib_1.__generator)(this, function (_b) {
switch (_b.label) {
case 0:
_a = this.getSizeAndViewport(), width = _a.width, height = _a.height, viewport = _a.viewport;
if (width <= 0 || height <= 0)
return [2 /*return*/];
return [4 /*yield*/, ctx.update('Rendering image...')];
case 1:
_b.sent();
imageData = this.imagePass.getImageData(width, height, viewport);
return [4 /*yield*/, ctx.update('Encoding image...')];
case 2:
_b.sent();
canvas = this.canvas;
canvas.width = imageData.width;
canvas.height = imageData.height;
canvasCtx = canvas.getContext('2d');
if (!canvasCtx)
throw new Error('Could not create canvas 2d context');
canvasCtx.putImageData(imageData, 0, 0);
return [2 /*return*/];
}
});
});
};
ViewportScreenshotHelper.prototype.copyToClipboardTask = function () {
var _this = this;
var cb = navigator.clipboard;
if (!(cb === null || cb === void 0 ? void 0 : cb.write)) {
this.plugin.log.error('clipboard.write not supported!');
return;
}
return mol_task_1.Task.create('Copy Image', function (ctx) { return (0, tslib_1.__awaiter)(_this, void 0, void 0, function () {
var blob, item;
return (0, tslib_1.__generator)(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.draw(ctx)];
case 1:
_a.sent();
return [4 /*yield*/, ctx.update('Converting image...')];
case 2:
_a.sent();
return [4 /*yield*/, (0, util_1.canvasToBlob)(this.canvas, 'png')];
case 3:
blob = _a.sent();
item = new ClipboardItem({ 'image/png': blob });
return [4 /*yield*/, cb.write([item])];
case 4:
_a.sent();
this.plugin.log.message('Image copied to clipboard.');
return [2 /*return*/];
}
});
}); });
};
ViewportScreenshotHelper.prototype.getImageDataUri = function () {
var _this = this;
return this.plugin.runTask(mol_task_1.Task.create('Generate Image', function (ctx) { return (0, tslib_1.__awaiter)(_this, void 0, void 0, function () {
return (0, tslib_1.__generator)(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.draw(ctx)];
case 1:
_a.sent();
return [4 /*yield*/, ctx.update('Converting image...')];
case 2:
_a.sent();
return [2 /*return*/, this.canvas.toDataURL('png')];
}
});
}); }));
};
ViewportScreenshotHelper.prototype.copyToClipboard = function () {
var task = this.copyToClipboardTask();
if (!task)
return;
return this.plugin.runTask(task);
};
ViewportScreenshotHelper.prototype.downloadTask = function (filename) {
var _this = this;
return mol_task_1.Task.create('Download Image', function (ctx) { return (0, tslib_1.__awaiter)(_this, void 0, void 0, function () {
var blob;
return (0, tslib_1.__generator)(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.draw(ctx)];
case 1:
_a.sent();
return [4 /*yield*/, ctx.update('Downloading image...')];
case 2:
_a.sent();
return [4 /*yield*/, (0, util_1.canvasToBlob)(this.canvas, 'png')];
case 3:
blob = _a.sent();
(0, download_1.download)(blob, filename !== null && filename !== void 0 ? filename : this.getFilename());
return [2 /*return*/];
}
});
}); });
};
ViewportScreenshotHelper.prototype.download = function (filename) {
this.plugin.runTask(this.downloadTask(filename));
};
return ViewportScreenshotHelper;
}(component_1.PluginComponent));
exports.ViewportScreenshotHelper = ViewportScreenshotHelper;
//# sourceMappingURL=viewport-screenshot.js.map