threepipe
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CanvasSnapshotPlugin.ts 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import {serialize, timeout} from 'ts-browser-helpers'
  2. import {AViewerPluginSync} from '../../viewer'
  3. import {uiButton, uiConfig, uiFolderContainer, uiInput} from 'uiconfig.js'
  4. import {CanvasSnapshot, CanvasSnapshotOptions} from '../../utils/canvas-snapshot'
  5. import {ProgressivePlugin} from '../pipeline/ProgressivePlugin'
  6. @uiFolderContainer('Canvas Snapshot (Image Export)')
  7. export class CanvasSnapshotPlugin extends AViewerPluginSync<''> {
  8. static readonly PluginType = 'CanvasSnapshotPlugin'
  9. enabled = true
  10. constructor() {
  11. super()
  12. this.downloadSnapshot = this.downloadSnapshot.bind(this)
  13. this.getDataUrl({})
  14. }
  15. /**
  16. * Returns a File object with screenshot of the viewer canvas
  17. * @param filename default is {@link CanvasSnapshotPlugin.filename}
  18. * @param options waitForProgressive: wait for progressive rendering to finish, default: true
  19. */
  20. async getFile(filename?: string, options: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {waitForProgressive: true}): Promise<File|undefined> {
  21. options.getDataUrl = false
  22. return await this._getFile(filename || this.filename, options) as File
  23. }
  24. /**
  25. * Returns a data url of the screenshot of the viewer canvas
  26. * @param options waitForProgressive: wait for progressive rendering to finish, default: true
  27. */
  28. async getDataUrl(options: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {}): Promise<string> {
  29. options.getDataUrl = true
  30. return await this._getFile('', options) as string ?? ''
  31. }
  32. private async _getFile(filename: string, options: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {}): Promise<File|string|undefined> {
  33. const viewer = this._viewer
  34. const canvas = this._viewer?.canvas
  35. if (!viewer || !canvas) return undefined
  36. const dpr = viewer.renderManager.renderScale
  37. if (options.displayPixelRatio !== undefined && options.displayPixelRatio !== dpr) {
  38. viewer.renderManager.renderScale = options.displayPixelRatio
  39. }
  40. if (options.timeout) await timeout(options.timeout)
  41. const progressive = viewer.getPlugin(ProgressivePlugin)
  42. if (options.waitForProgressive !== false && progressive) {
  43. // todo: disable interactions and all so that frameCount is not affected
  44. await new Promise<void>((res)=>{
  45. const listener = () => {
  46. if (!progressive.isConverged(true)) return
  47. viewer.removeEventListener('postFrame', listener)
  48. res()
  49. }
  50. viewer.addEventListener('postFrame', listener)
  51. })
  52. } else await viewer.doOnce('postFrame')
  53. options.displayPixelRatio = 1
  54. const rect = options.rect
  55. if (rect && viewer.renderManager.renderScale !== 1) {
  56. options.rect = {
  57. ...rect,
  58. x: rect.x * viewer.renderManager.renderScale,
  59. y: rect.y * viewer.renderManager.renderScale,
  60. width: rect.width * viewer.renderManager.renderScale,
  61. height: rect.height * viewer.renderManager.renderScale,
  62. }
  63. }
  64. const file = await CanvasSnapshot.GetFile(canvas, filename, options)
  65. options.rect = rect
  66. options.displayPixelRatio = viewer.renderManager.renderScale
  67. viewer.renderManager.renderScale = dpr
  68. return file
  69. }
  70. @uiInput('Filename')
  71. @serialize()
  72. filename = 'snapshot.png'
  73. /**
  74. * Only for {@link downloadSnapshot} and functions using that
  75. */
  76. @uiConfig()
  77. @serialize()
  78. defaultOptions: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {
  79. waitForProgressive: true,
  80. displayPixelRatio: window.devicePixelRatio,
  81. scale: 1,
  82. timeout: 0,
  83. quality: 0.9,
  84. }
  85. @uiButton('Download .png')
  86. async downloadSnapshot(filename?: string, options: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {waitForProgressive: true}): Promise<void> {
  87. if (!this._viewer) return
  88. if (!options.mimeType && !filename) this.filename = this.filename.split('.').slice(0, -1).join('.') + '.png'
  89. const file = await this.getFile(filename, {...this.defaultOptions, ...options})
  90. if (file) await this._viewer.exportBlob(file, file.name)
  91. }
  92. @uiButton('Download .jpeg')
  93. protected async _downloadJpeg(): Promise<void> {
  94. this.filename = this.filename.split('.').slice(0, -1).join('.') + '.jpeg'
  95. return this.downloadSnapshot(undefined, {mimeType: 'image/jpeg'})
  96. }
  97. @uiButton('Download .webp')
  98. protected async _downloadWebp(): Promise<void> {
  99. this.filename = this.filename.split('.').slice(0, -1).join('.') + '.webp'
  100. return this.downloadSnapshot(undefined, {mimeType: 'image/webp'})
  101. }
  102. }
  103. /**
  104. * @deprecated - use {@link CanvasSnapshotPlugin}
  105. */
  106. export class CanvasSnipperPlugin extends CanvasSnapshotPlugin {
  107. static readonly PluginType: any = 'CanvasSnipper'
  108. constructor() {
  109. super()
  110. console.warn('CanvasSnipperPlugin is deprecated, use CanvasSnapshotPlugin')
  111. }
  112. }