UNPKG

molstar

Version:

A comprehensive macromolecular library.

413 lines 19.2 kB
"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