UNPKG

@shopify/react-native-skia

Version:

High-performance React Native Graphics using Skia

323 lines (317 loc) 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SkiaPictureView = void 0; var _react = _interopRequireWildcard(require("react")); var _JsiSkSurface = require("../skia/web/JsiSkSurface"); var _Platform = require("../Platform"); var _SkiaViewNativeId = require("./SkiaViewNativeId"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* global HTMLCanvasElement */ const dp2Pixel = (pd, rect) => { if (!rect) { return undefined; } return { x: rect.x * pd, y: rect.y * pd, width: rect.width * pd, height: rect.height * pd }; }; class WebGLRenderer { constructor(canvas, pd) { this.canvas = canvas; this.pd = pd; _defineProperty(this, "surface", null); this.onResize(); } makeImageSnapshot(picture, rect) { if (!this.surface) { return null; } const canvas = this.surface.getCanvas(); canvas.clear(CanvasKit.TRANSPARENT); this.draw(picture); this.surface.ref.flush(); return this.surface.makeImageSnapshot(dp2Pixel(this.pd, rect)); } onResize() { const { canvas, pd } = this; canvas.width = canvas.clientWidth * pd; canvas.height = canvas.clientHeight * pd; const surface = CanvasKit.MakeWebGLCanvasSurface(canvas); const ctx = canvas.getContext("webgl2"); if (ctx) { ctx.drawingBufferColorSpace = "display-p3"; } if (!surface) { throw new Error("Could not create surface"); } this.surface = new _JsiSkSurface.JsiSkSurface(CanvasKit, surface); } draw(picture) { if (this.surface) { const canvas = this.surface.getCanvas(); canvas.clear(Float32Array.of(0, 0, 0, 0)); canvas.save(); canvas.scale(pd, pd); canvas.drawPicture(picture); canvas.restore(); this.surface.ref.flush(); } } dispose() { if (this.surface) { var _this$canvas; (_this$canvas = this.canvas) === null || _this$canvas === void 0 || (_this$canvas = _this$canvas.getContext("webgl2")) === null || _this$canvas === void 0 || (_this$canvas = _this$canvas.getExtension("WEBGL_lose_context")) === null || _this$canvas === void 0 || _this$canvas.loseContext(); this.surface.ref.delete(); this.surface = null; } } } class StaticWebGLRenderer { constructor(canvas, pd) { this.canvas = canvas; this.pd = pd; _defineProperty(this, "cachedImage", null); } onResize() { this.cachedImage = null; } renderPictureToSurface(picture) { const tempCanvas = new OffscreenCanvas(this.canvas.clientWidth * this.pd, this.canvas.clientHeight * this.pd); let surface = null; try { const webglSurface = CanvasKit.MakeWebGLCanvasSurface(tempCanvas); const ctx = tempCanvas.getContext("webgl2"); if (ctx) { ctx.drawingBufferColorSpace = "display-p3"; } if (!webglSurface) { throw new Error("Could not create WebGL surface"); } surface = new _JsiSkSurface.JsiSkSurface(CanvasKit, webglSurface); const skiaCanvas = surface.getCanvas(); skiaCanvas.clear(Float32Array.of(0, 0, 0, 0)); skiaCanvas.save(); skiaCanvas.scale(this.pd, this.pd); skiaCanvas.drawPicture(picture); skiaCanvas.restore(); surface.ref.flush(); return { surface, tempCanvas }; } catch (error) { if (surface) { surface.ref.delete(); } this.cleanupWebGLContext(tempCanvas); return null; } } cleanupWebGLContext(tempCanvas) { const ctx = tempCanvas.getContext("webgl2"); if (ctx) { const loseContext = ctx.getExtension("WEBGL_lose_context"); if (loseContext) { loseContext.loseContext(); } } } draw(picture) { const renderResult = this.renderPictureToSurface(picture); if (!renderResult) { return; } const { tempCanvas } = renderResult; const ctx2d = this.canvas.getContext("2d"); if (!ctx2d) { throw new Error("Could not get 2D context"); } // Set canvas dimensions to match pixel density this.canvas.width = this.canvas.clientWidth * this.pd; this.canvas.height = this.canvas.clientHeight * this.pd; // Draw the tempCanvas scaled down to the display size ctx2d.drawImage(tempCanvas, 0, 0, tempCanvas.width, tempCanvas.height, 0, 0, this.canvas.clientWidth * this.pd, this.canvas.clientHeight * this.pd); this.cleanupWebGLContext(tempCanvas); } makeImageSnapshot(picture, rect) { if (!this.cachedImage) { const renderResult = this.renderPictureToSurface(picture); if (!renderResult) { return null; } const { surface, tempCanvas } = renderResult; try { this.cachedImage = surface.makeImageSnapshot(dp2Pixel(this.pd, rect)); } catch (error) { console.error("Error creating image snapshot:", error); } finally { surface.ref.delete(); this.cleanupWebGLContext(tempCanvas); } } return this.cachedImage; } dispose() { var _this$cachedImage; (_this$cachedImage = this.cachedImage) === null || _this$cachedImage === void 0 || _this$cachedImage.dispose(); this.cachedImage = null; } } const pd = _Platform.Platform.PixelRatio; const SkiaPictureView = props => { const { ref } = props; const canvasRef = (0, _react.useRef)(null); const renderer = (0, _react.useRef)(null); const redrawRequestsRef = (0, _react.useRef)(0); const requestIdRef = (0, _react.useRef)(0); const pictureRef = (0, _react.useRef)(null); const { picture, onLayout } = props; const redraw = (0, _react.useCallback)(() => { redrawRequestsRef.current++; }, []); const getSize = (0, _react.useCallback)(() => { var _canvasRef$current, _canvasRef$current2; return { width: ((_canvasRef$current = canvasRef.current) === null || _canvasRef$current === void 0 ? void 0 : _canvasRef$current.clientWidth) || 0, height: ((_canvasRef$current2 = canvasRef.current) === null || _canvasRef$current2 === void 0 ? void 0 : _canvasRef$current2.clientHeight) || 0 }; }, []); const setPicture = (0, _react.useCallback)(newPicture => { pictureRef.current = newPicture; redraw(); }, [redraw]); const makeImageSnapshot = (0, _react.useCallback)(rect => { if (renderer.current && pictureRef.current) { return renderer.current.makeImageSnapshot(pictureRef.current, rect); } return null; }, []); const measure = (0, _react.useCallback)(callback => { if (canvasRef.current) { const rect = canvasRef.current.getBoundingClientRect(); const parentElement = canvasRef.current.offsetParent; const parentRect = (parentElement === null || parentElement === void 0 ? void 0 : parentElement.getBoundingClientRect()) || { left: 0, top: 0 }; // x, y are relative to the parent const x = rect.left - parentRect.left; const y = rect.top - parentRect.top; // pageX, pageY are absolute screen coordinates const pageX = rect.left + window.scrollX; const pageY = rect.top + window.scrollY; callback(x, y, rect.width, rect.height, pageX, pageY); } }, []); const measureInWindow = (0, _react.useCallback)(callback => { if (canvasRef.current) { const rect = canvasRef.current.getBoundingClientRect(); // x, y are the absolute coordinates in the window const x = rect.left; const y = rect.top; callback(x, y, rect.width, rect.height); } }, []); const tick = (0, _react.useCallback)(() => { if (redrawRequestsRef.current > 0) { redrawRequestsRef.current = 0; if (renderer.current && pictureRef.current) { renderer.current.draw(pictureRef.current); } } requestIdRef.current = requestAnimationFrame(tick); }, []); const onLayoutEvent = (0, _react.useCallback)(evt => { const canvas = canvasRef.current; if (canvas) { renderer.current = props.__destroyWebGLContextAfterRender === true ? new StaticWebGLRenderer(canvas, pd) : new WebGLRenderer(canvas, pd); if (pictureRef.current) { renderer.current.draw(pictureRef.current); } } if (onLayout) { onLayout(evt); } }, [onLayout, props.__destroyWebGLContextAfterRender]); (0, _react.useImperativeHandle)(ref, () => ({ setPicture, getSize, redraw, makeImageSnapshot, measure, measureInWindow, get canvasRef() { return () => canvasRef.current; } }), [setPicture, getSize, redraw, makeImageSnapshot, measure, measureInWindow]); (0, _react.useEffect)(() => { var _props$nativeID; const nativeID = (_props$nativeID = props.nativeID) !== null && _props$nativeID !== void 0 ? _props$nativeID : `${_SkiaViewNativeId.SkiaViewNativeId.current++}`; global.SkiaViewApi.registerView(nativeID, { setPicture, getSize, redraw, makeImageSnapshot, measure, measureInWindow }); }, [setPicture, getSize, redraw, makeImageSnapshot, measure, measureInWindow, props.nativeID]); (0, _react.useEffect)(() => { if (props.picture) { setPicture(props.picture); } }, [setPicture, props.picture]); (0, _react.useEffect)(() => { tick(); return () => { cancelAnimationFrame(requestIdRef.current); if (renderer.current) { renderer.current.dispose(); renderer.current = null; } }; }, [tick]); (0, _react.useEffect)(() => { if (renderer.current && pictureRef.current) { renderer.current.draw(pictureRef.current); } }, [picture, redraw]); const { debug = false, ref: _ref, ...viewProps } = props; return /*#__PURE__*/_react.default.createElement(_Platform.Platform.View, _extends({}, viewProps, { onLayout: onLayoutEvent }), /*#__PURE__*/_react.default.createElement("canvas", { ref: canvasRef, style: { display: "block", width: "100%", height: "100%" } })); }; exports.SkiaPictureView = SkiaPictureView; //# sourceMappingURL=SkiaPictureView.web.js.map