screenshotTools.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import { Nullable } from "../types";
  2. import { Camera } from "../Cameras/camera";
  3. import { Texture } from "../Materials/Textures/texture";
  4. import { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture";
  5. import { FxaaPostProcess } from "../PostProcesses/fxaaPostProcess";
  6. import { Constants } from "../Engines/constants";
  7. import { Logger } from "./logger";
  8. import { _TypeStore } from "./typeStore";
  9. import { Tools } from "./tools";
  10. import { IScreenshotSize } from './interfaces/screenshotSize';
  11. declare type Engine = import("../Engines/engine").Engine;
  12. /**
  13. * Class containing a set of static utilities functions for screenshots
  14. */
  15. export class ScreenshotTools {
  16. /**
  17. * Captures a screenshot of the current rendering
  18. * @see https://doc.babylonjs.com/how_to/render_scene_on_a_png
  19. * @param engine defines the rendering engine
  20. * @param camera defines the source camera
  21. * @param size This parameter can be set to a single number or to an object with the
  22. * following (optional) properties: precision, width, height. If a single number is passed,
  23. * it will be used for both width and height. If an object is passed, the screenshot size
  24. * will be derived from the parameters. The precision property is a multiplier allowing
  25. * rendering at a higher or lower resolution
  26. * @param successCallback defines the callback receives a single parameter which contains the
  27. * screenshot as a string of base64-encoded characters. This string can be assigned to the
  28. * src parameter of an <img> to display it
  29. * @param mimeType defines the MIME type of the screenshot image (default: image/png).
  30. * Check your browser for supported MIME types
  31. */
  32. public static CreateScreenshot(engine: Engine, camera: Camera, size: IScreenshotSize | number, successCallback?: (data: string) => void, mimeType: string = "image/png"): void {
  33. const { height, width } = ScreenshotTools._getScreenshotSize(engine, camera, size);
  34. if (!(height && width)) {
  35. Logger.Error("Invalid 'size' parameter !");
  36. return;
  37. }
  38. if (!Tools._ScreenshotCanvas) {
  39. Tools._ScreenshotCanvas = document.createElement('canvas');
  40. }
  41. Tools._ScreenshotCanvas.width = width;
  42. Tools._ScreenshotCanvas.height = height;
  43. var renderContext = Tools._ScreenshotCanvas.getContext("2d");
  44. var ratio = engine.getRenderWidth() / engine.getRenderHeight();
  45. var newWidth = width;
  46. var newHeight = newWidth / ratio;
  47. if (newHeight > height) {
  48. newHeight = height;
  49. newWidth = newHeight * ratio;
  50. }
  51. var offsetX = Math.max(0, width - newWidth) / 2;
  52. var offsetY = Math.max(0, height - newHeight) / 2;
  53. engine.onEndFrameObservable.addOnce(() => {
  54. var renderingCanvas = engine.getRenderingCanvas();
  55. if (renderContext && renderingCanvas) {
  56. renderContext.drawImage(renderingCanvas, offsetX, offsetY, newWidth, newHeight);
  57. }
  58. Tools.EncodeScreenshotCanvasData(successCallback, mimeType);
  59. });
  60. }
  61. /**
  62. * Captures a screenshot of the current rendering
  63. * @see https://doc.babylonjs.com/how_to/render_scene_on_a_png
  64. * @param engine defines the rendering engine
  65. * @param camera defines the source camera
  66. * @param size This parameter can be set to a single number or to an object with the
  67. * following (optional) properties: precision, width, height. If a single number is passed,
  68. * it will be used for both width and height. If an object is passed, the screenshot size
  69. * will be derived from the parameters. The precision property is a multiplier allowing
  70. * rendering at a higher or lower resolution
  71. * @param mimeType defines the MIME type of the screenshot image (default: image/png).
  72. * Check your browser for supported MIME types
  73. * @returns screenshot as a string of base64-encoded characters. This string can be assigned
  74. * to the src parameter of an <img> to display it
  75. */
  76. public static CreateScreenshotAsync(engine: Engine, camera: Camera, size: any, mimeType: string = "image/png"): Promise<string> {
  77. return new Promise((resolve, reject) => {
  78. ScreenshotTools.CreateScreenshot(engine, camera, size, (data) => {
  79. if (typeof(data) !== "undefined") {
  80. resolve(data);
  81. } else {
  82. reject(new Error("Data is undefined"));
  83. }
  84. }, mimeType);
  85. });
  86. }
  87. /**
  88. * Generates an image screenshot from the specified camera.
  89. * @see https://doc.babylonjs.com/how_to/render_scene_on_a_png
  90. * @param engine The engine to use for rendering
  91. * @param camera The camera to use for rendering
  92. * @param size This parameter can be set to a single number or to an object with the
  93. * following (optional) properties: precision, width, height. If a single number is passed,
  94. * it will be used for both width and height. If an object is passed, the screenshot size
  95. * will be derived from the parameters. The precision property is a multiplier allowing
  96. * rendering at a higher or lower resolution
  97. * @param successCallback The callback receives a single parameter which contains the
  98. * screenshot as a string of base64-encoded characters. This string can be assigned to the
  99. * src parameter of an <img> to display it
  100. * @param mimeType The MIME type of the screenshot image (default: image/png).
  101. * Check your browser for supported MIME types
  102. * @param samples Texture samples (default: 1)
  103. * @param antialiasing Whether antialiasing should be turned on or not (default: false)
  104. * @param fileName A name for for the downloaded file.
  105. * @param renderSprites Whether the sprites should be rendered or not (default: false)
  106. * @param enableStencilBuffer Whether the stencil buffer should be enabled or not (default: false)
  107. */
  108. public static CreateScreenshotUsingRenderTarget(engine: Engine, camera: Camera, size: IScreenshotSize | number, successCallback?: (data: string) => void, mimeType: string = "image/png", samples: number = 1, antialiasing: boolean = false, fileName?: string, renderSprites: boolean = false, enableStencilBuffer: boolean = false): void {
  109. const { height, width } = ScreenshotTools._getScreenshotSize(engine, camera, size);
  110. let targetTextureSize = { width, height };
  111. if (!(height && width)) {
  112. Logger.Error("Invalid 'size' parameter !");
  113. return;
  114. }
  115. var scene = camera.getScene();
  116. var previousCamera: Nullable<Camera> = null;
  117. var previousCameras = scene.activeCameras;
  118. if (scene.activeCamera !== camera || scene.activeCameras && scene.activeCameras.length) {
  119. previousCamera = scene.activeCamera;
  120. scene.activeCamera = camera;
  121. }
  122. // At this point size can be a number, or an object (according to engine.prototype.createRenderTargetTexture method)
  123. var texture = new RenderTargetTexture("screenShot", targetTextureSize, scene, false, false, Constants.TEXTURETYPE_UNSIGNED_INT, false, Texture.NEAREST_SAMPLINGMODE, undefined, enableStencilBuffer, undefined, undefined, undefined, samples);
  124. texture.renderList = null;
  125. texture.samples = samples;
  126. texture.renderSprites = renderSprites;
  127. engine.onEndFrameObservable.addOnce(() => {
  128. texture.readPixels(undefined, undefined, undefined, false)!.then((data) => {
  129. Tools.DumpData(width, height, data, successCallback as (data: string | ArrayBuffer) => void, mimeType, fileName, true);
  130. texture.dispose();
  131. if (previousCamera) {
  132. scene.activeCamera = previousCamera;
  133. }
  134. scene.activeCameras = previousCameras;
  135. camera.getProjectionMatrix(true); // Force cache refresh;
  136. });
  137. });
  138. const renderToTexture = () => {
  139. scene.incrementRenderId();
  140. scene.resetCachedMaterial();
  141. texture.render(true);
  142. };
  143. if (antialiasing) {
  144. const fxaaPostProcess = new FxaaPostProcess('antialiasing', 1.0, scene.activeCamera);
  145. texture.addPostProcess(fxaaPostProcess);
  146. // Async Shader Compilation can lead to none ready effects in synchronous code
  147. if (!fxaaPostProcess.getEffect().isReady()) {
  148. fxaaPostProcess.getEffect().onCompiled = () => {
  149. renderToTexture();
  150. };
  151. }
  152. // The effect is ready we can render
  153. else {
  154. renderToTexture();
  155. }
  156. }
  157. else {
  158. // No need to wait for extra resources to be ready
  159. renderToTexture();
  160. }
  161. }
  162. /**
  163. * Generates an image screenshot from the specified camera.
  164. * @see https://doc.babylonjs.com/how_to/render_scene_on_a_png
  165. * @param engine The engine to use for rendering
  166. * @param camera The camera to use for rendering
  167. * @param size This parameter can be set to a single number or to an object with the
  168. * following (optional) properties: precision, width, height. If a single number is passed,
  169. * it will be used for both width and height. If an object is passed, the screenshot size
  170. * will be derived from the parameters. The precision property is a multiplier allowing
  171. * rendering at a higher or lower resolution
  172. * @param mimeType The MIME type of the screenshot image (default: image/png).
  173. * Check your browser for supported MIME types
  174. * @param samples Texture samples (default: 1)
  175. * @param antialiasing Whether antialiasing should be turned on or not (default: false)
  176. * @param fileName A name for for the downloaded file.
  177. * @param renderSprites Whether the sprites should be rendered or not (default: false)
  178. * @returns screenshot as a string of base64-encoded characters. This string can be assigned
  179. * to the src parameter of an <img> to display it
  180. */
  181. public static CreateScreenshotUsingRenderTargetAsync(engine: Engine, camera: Camera, size: any, mimeType: string = "image/png", samples: number = 1, antialiasing: boolean = false, fileName?: string, renderSprites: boolean = false): Promise<string> {
  182. return new Promise((resolve, reject) => {
  183. ScreenshotTools.CreateScreenshotUsingRenderTarget(engine, camera, size, (data) => {
  184. if (typeof(data) !== "undefined") {
  185. resolve(data);
  186. } else {
  187. reject(new Error("Data is undefined"));
  188. }
  189. }, mimeType, samples, antialiasing, fileName, renderSprites);
  190. });
  191. }
  192. /**
  193. * Gets height and width for screenshot size
  194. * @private
  195. */
  196. private static _getScreenshotSize(engine: Engine, camera: Camera, size: IScreenshotSize | number): {height: number, width: number} {
  197. let height = 0;
  198. let width = 0;
  199. //If a size value defined as object
  200. if (typeof(size) === 'object') {
  201. const precision = size.precision
  202. ? Math.abs(size.precision) // prevent GL_INVALID_VALUE : glViewport: negative width/height
  203. : 1;
  204. //If a width and height values is specified
  205. if (size.width && size.height) {
  206. height = size.height * precision;
  207. width = size.width * precision;
  208. }
  209. //If passing only width, computing height to keep display canvas ratio.
  210. else if (size.width && !size.height) {
  211. width = size.width * precision;
  212. height = Math.round(width / engine.getAspectRatio(camera));
  213. }
  214. //If passing only height, computing width to keep display canvas ratio.
  215. else if (size.height && !size.width) {
  216. height = size.height * precision;
  217. width = Math.round(height * engine.getAspectRatio(camera));
  218. }
  219. else {
  220. width = Math.round(engine.getRenderWidth() * precision);
  221. height = Math.round(width / engine.getAspectRatio(camera));
  222. }
  223. }
  224. //Assuming here that "size" parameter is a number
  225. else if (!isNaN(size)) {
  226. height = size;
  227. width = size;
  228. }
  229. // When creating the image data from the CanvasRenderingContext2D, the width and height is clamped to the size of the _gl context
  230. // On certain GPUs, it seems as if the _gl context truncates to an integer automatically. Therefore, if a user tries to pass the width of their canvas element
  231. // and it happens to be a float (1000.5 x 600.5 px), the engine.readPixels will return a different size array than context.createImageData
  232. // to resolve this, we truncate the floats here to ensure the same size
  233. if (width) {
  234. width = Math.floor(width);
  235. }
  236. if (height) {
  237. height = Math.floor(height);
  238. }
  239. return { height: height | 0, width: width | 0 };
  240. }
  241. }
  242. Tools.CreateScreenshot = ScreenshotTools.CreateScreenshot;
  243. Tools.CreateScreenshotAsync = ScreenshotTools.CreateScreenshotAsync;
  244. Tools.CreateScreenshotUsingRenderTarget = ScreenshotTools.CreateScreenshotUsingRenderTarget;
  245. Tools.CreateScreenshotUsingRenderTargetAsync = ScreenshotTools.CreateScreenshotUsingRenderTargetAsync;