UNPKG

molstar

Version:

A comprehensive macromolecular library.

220 lines (219 loc) 9.68 kB
"use strict"; /** * Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Jesse Liang <jesse.liang@rcsb.org> * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org> * @author Ke Ma <mark.ma@rcsb.org> * @author Adam Midlik <midlik@gmail.com> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.STYLIZED_POSTPROCESSING = exports.HeadlessScreenshotHelper = void 0; exports.defaultCanvas3DParams = defaultCanvas3DParams; exports.defaultWebGLAttributes = defaultWebGLAttributes; exports.defaultImagePassParams = defaultImagePassParams; const tslib_1 = require("tslib"); const fs_1 = tslib_1.__importDefault(require("fs")); const path_1 = tslib_1.__importDefault(require("path")); const canvas3d_1 = require("../../mol-canvas3d/canvas3d"); const passes_1 = require("../../mol-canvas3d/passes/passes"); const postprocessing_1 = require("../../mol-canvas3d/passes/postprocessing"); const context_1 = require("../../mol-gl/webgl/context"); const assets_1 = require("../../mol-util/assets"); const names_1 = require("../../mol-util/color/names"); const image_1 = require("../../mol-util/image"); const input_observer_1 = require("../../mol-util/input/input-observer"); const param_definition_1 = require("../../mol-util/param-definition"); /** To render Canvas3D when running in Node.js (without DOM) */ class HeadlessScreenshotHelper { constructor(externalModules, canvasSize, canvas3d, options) { var _a, _b, _c; this.externalModules = externalModules; this.canvasSize = canvasSize; if (canvas3d) { this.canvas3d = canvas3d; } else { const glContext = this.externalModules.gl(this.canvasSize.width, this.canvasSize.height, (_a = options === null || options === void 0 ? void 0 : options.webgl) !== null && _a !== void 0 ? _a : defaultWebGLAttributes()); const webgl = (0, context_1.createContext)(glContext); const input = input_observer_1.InputObserver.create(); const attribs = { ...canvas3d_1.Canvas3DContext.DefaultAttribs }; const props = { ...canvas3d_1.Canvas3DContext.DefaultProps }; const assetManager = new assets_1.AssetManager(); const passes = new passes_1.Passes(webgl, assetManager, props); const pixelScale = 1; const syncPixelScale = () => { }; const setProps = () => { }; const dispose = () => { input.dispose(); webgl.destroy(); }; this.canvas3d = canvas3d_1.Canvas3D.create({ webgl, input, passes, attribs, props, assetManager, pixelScale, syncPixelScale, setProps, dispose }, (_b = options === null || options === void 0 ? void 0 : options.canvas) !== null && _b !== void 0 ? _b : defaultCanvas3DParams()); } this.imagePass = this.canvas3d.getImagePass((_c = options === null || options === void 0 ? void 0 : options.imagePass) !== null && _c !== void 0 ? _c : defaultImagePassParams()); this.imagePass.setSize(this.canvasSize.width, this.canvasSize.height); } async getImageData(runtime, width, height) { this.imagePass.setSize(width, height); await this.imagePass.render(runtime); this.imagePass.colorTarget.bind(); const array = new Uint8Array(width * height * 4); this.canvas3d.webgl.readPixels(0, 0, width, height, array); const pixelData = image_1.PixelData.create(array, width, height); image_1.PixelData.flipY(pixelData); image_1.PixelData.divideByAlpha(pixelData); // ImageData is not defined in Node.js return { data: new Uint8ClampedArray(array), width, height }; } async getImageRaw(runtime, imageSize, postprocessing) { var _a, _b; const width = (_a = imageSize === null || imageSize === void 0 ? void 0 : imageSize.width) !== null && _a !== void 0 ? _a : this.canvasSize.width; const height = (_b = imageSize === null || imageSize === void 0 ? void 0 : imageSize.height) !== null && _b !== void 0 ? _b : this.canvasSize.height; this.canvas3d.commit(true); this.imagePass.setProps({ postprocessing: param_definition_1.ParamDefinition.merge(postprocessing_1.PostprocessingParams, this.canvas3d.props.postprocessing, postprocessing), }); return this.getImageData(runtime, width, height); } async getImagePng(runtime, imageSize, postprocessing) { const imageData = await this.getImageRaw(runtime, imageSize, postprocessing); if (!this.externalModules.pngjs) { throw new Error("External module 'pngjs' was not provided. If you want to use getImagePng, you must import 'pngjs' and provide it to the HeadlessPluginContext/HeadlessScreenshotHelper constructor."); } const generatedPng = new this.externalModules.pngjs.PNG({ width: imageData.width, height: imageData.height }); generatedPng.data = Buffer.from(imageData.data.buffer); return generatedPng; } async getImageJpeg(runtime, imageSize, postprocessing, jpegQuality = 90) { const imageData = await this.getImageRaw(runtime, imageSize, postprocessing); if (!this.externalModules['jpeg-js']) { throw new Error("External module 'jpeg-js' was not provided. If you want to use getImageJpeg, you must import 'jpeg-js' and provide it to the HeadlessPluginContext/HeadlessScreenshotHelper constructor."); } const generatedJpeg = this.externalModules['jpeg-js'].encode(imageData, jpegQuality); return generatedJpeg; } async saveImage(runtime, outPath, imageSize, postprocessing, format, jpegQuality = 90) { if (!format) { const extension = path_1.default.extname(outPath).toLowerCase(); if (extension === '.png') format = 'png'; else if (extension === '.jpg' || extension === '.jpeg') format = 'jpeg'; else throw new Error(`Cannot guess image format from file path '${outPath}'. Specify format explicitly or use path with one of these extensions: .png, .jpg, .jpeg`); } if (format === 'png') { const generatedPng = await this.getImagePng(runtime, imageSize, postprocessing); await writePngFile(generatedPng, outPath); } else if (format === 'jpeg') { const generatedJpeg = await this.getImageJpeg(runtime, imageSize, postprocessing, jpegQuality); await writeJpegFile(generatedJpeg, outPath); } else { throw new Error(`Invalid format: ${format}`); } } } exports.HeadlessScreenshotHelper = HeadlessScreenshotHelper; async function writePngFile(png, outPath) { await new Promise(resolve => { png.pack().pipe(fs_1.default.createWriteStream(outPath)).on('finish', resolve); }); } async function writeJpegFile(jpeg, outPath) { await new Promise(resolve => { fs_1.default.writeFile(outPath, jpeg.data, () => resolve()); }); } function defaultCanvas3DParams() { return { camera: { mode: 'orthographic', helper: { axes: { name: 'off', params: {} } }, stereo: { name: 'off', params: {} }, fov: 90, manualReset: false, }, cameraResetDurationMs: 0, cameraFog: { name: 'on', params: { intensity: 50 } }, renderer: { ...canvas3d_1.DefaultCanvas3DParams.renderer, backgroundColor: names_1.ColorNames.white, }, postprocessing: { ...canvas3d_1.DefaultCanvas3DParams.postprocessing, occlusion: { name: 'off', params: {} }, outline: { name: 'off', params: {} }, antialiasing: { name: 'fxaa', params: { edgeThresholdMin: 0.0312, edgeThresholdMax: 0.063, iterations: 12, subpixelQuality: 0.3 } }, background: { variant: { name: 'off', params: {} } }, shadow: { name: 'off', params: {} }, } }; } function defaultWebGLAttributes() { return { antialias: true, preserveDrawingBuffer: true, alpha: true, // the renderer requires an alpha channel depth: true, // the renderer requires a depth buffer premultipliedAlpha: true, // the renderer outputs PMA }; } function defaultImagePassParams() { return { cameraHelper: { axes: { name: 'off', params: {} }, }, multiSample: { ...canvas3d_1.DefaultCanvas3DParams.multiSample, mode: 'on', sampleLevel: 4, reuseOcclusion: false, } }; } exports.STYLIZED_POSTPROCESSING = { occlusion: { name: 'on', params: { samples: 32, multiScale: { name: 'off', params: {} }, radius: 5, bias: 0.8, blurKernelSize: 15, blurDepthBias: 0.5, resolutionScale: 1, color: names_1.ColorNames.black, transparentThreshold: 0.4, } }, outline: { name: 'on', params: { scale: 1, threshold: 0.95, color: names_1.ColorNames.black, includeTransparent: true, } } };