UNPKG

@shopify/react-native-skia

Version:

High-performance React Native Graphics using Skia

351 lines (336 loc) 8.93 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import type { CTMProps, DrawingNodeProps, BoxShadowProps, } from "../../dom/types"; import { NodeType } from "../../dom/types"; import type { BaseRecorder } from "../../skia/types/Recorder"; import type { Node } from "../Node"; import { isImageFilter, isShader, sortNodeChildren } from "../Node"; import type { AnimatedProps } from "../../renderer"; export const processPaint = ({ opacity, color, strokeWidth, blendMode, style, strokeJoin, strokeCap, strokeMiter, antiAlias, dither, paint: paintRef, }: DrawingNodeProps) => { const paint: DrawingNodeProps = {}; if (opacity !== undefined) { paint.opacity = opacity; } if (color !== undefined) { paint.color = color; } if (strokeWidth !== undefined) { paint.strokeWidth = strokeWidth; } if (blendMode !== undefined) { paint.blendMode = blendMode; } if (style !== undefined) { paint.style = style; } if (strokeJoin !== undefined) { paint.strokeJoin = strokeJoin; } if (strokeCap !== undefined) { paint.strokeCap = strokeCap; } if (strokeMiter !== undefined) { paint.strokeMiter = strokeMiter; } if (antiAlias !== undefined) { paint.antiAlias = antiAlias; } if (dither !== undefined) { paint.dither = dither; } if (paintRef !== undefined) { paint.paint = paintRef; } if ( opacity !== undefined || color !== undefined || strokeWidth !== undefined || blendMode !== undefined || style !== undefined || strokeJoin !== undefined || strokeCap !== undefined || strokeMiter !== undefined || antiAlias !== undefined || dither !== undefined || paintRef !== undefined ) { return paint; } return null; }; const processCTM = ({ clip, invertClip, transform, origin, matrix, layer, }: CTMProps) => { const ctm: CTMProps = {}; if (clip) { ctm.clip = clip; } if (invertClip) { ctm.invertClip = invertClip; } if (transform) { ctm.transform = transform; } if (origin) { ctm.origin = origin; } if (matrix) { ctm.matrix = matrix; } if (layer) { ctm.layer = layer; } if ( clip !== undefined || invertClip !== undefined || transform !== undefined || origin !== undefined || matrix !== undefined || layer !== undefined ) { return ctm; } return null; }; const pushColorFilters = ( recorder: BaseRecorder, colorFilters: Node<any>[] ) => { colorFilters.forEach((colorFilter) => { if (colorFilter.children.length > 0) { pushColorFilters(recorder, colorFilter.children); } recorder.pushColorFilter(colorFilter.type, colorFilter.props); const needsComposition = colorFilter.type !== NodeType.LerpColorFilter && colorFilter.children.length > 0; if (needsComposition) { recorder.composeColorFilter(); } }); }; const pushPathEffects = (recorder: BaseRecorder, pathEffects: Node<any>[]) => { pathEffects.forEach((pathEffect) => { if (pathEffect.children.length > 0) { pushPathEffects(recorder, pathEffect.children); } recorder.pushPathEffect(pathEffect.type, pathEffect.props); const needsComposition = pathEffect.type !== NodeType.SumPathEffect && pathEffect.children.length > 0; if (needsComposition) { recorder.composePathEffect(); } }); }; const pushImageFilters = ( recorder: BaseRecorder, imageFilters: Node<any>[] ) => { imageFilters.forEach((imageFilter) => { if (imageFilter.children.length > 0) { pushImageFilters(recorder, imageFilter.children); } if (isImageFilter(imageFilter.type)) { recorder.pushImageFilter(imageFilter.type, imageFilter.props); } else if (isShader(imageFilter.type)) { recorder.pushShader(imageFilter.type, imageFilter.props, 0); } const needsComposition = imageFilter.type !== NodeType.BlendImageFilter && imageFilter.children.length > 0; if (needsComposition) { recorder.composeImageFilter(); } }); }; const pushShaders = (recorder: BaseRecorder, shaders: Node<any>[]) => { shaders.forEach((shader) => { if (shader.children.length > 0) { pushShaders(recorder, shader.children); } recorder.pushShader(shader.type, shader.props, shader.children.length); }); }; const pushMaskFilters = (recorder: BaseRecorder, maskFilters: Node<any>[]) => { if (maskFilters.length > 0) { recorder.pushBlurMaskFilter(maskFilters[maskFilters.length - 1].props); } }; const pushPaints = (recorder: BaseRecorder, paints: Node<any>[]) => { paints.forEach((paint) => { recorder.savePaint(paint.props, true); const { colorFilters, maskFilters, shaders, imageFilters, pathEffects } = sortNodeChildren(paint); pushColorFilters(recorder, colorFilters); pushImageFilters(recorder, imageFilters); pushMaskFilters(recorder, maskFilters); pushShaders(recorder, shaders); pushPathEffects(recorder, pathEffects); recorder.restorePaintDeclaration(); }); }; type StackingContextProps = Pick<DrawingNodeProps, "zIndex">; const getStackingContextProps = ( props: AnimatedProps<DrawingNodeProps> ): AnimatedProps<StackingContextProps> | undefined => { const { zIndex } = props; if (zIndex === undefined) { return undefined; } return { zIndex }; }; const visitNode = (recorder: BaseRecorder, node: Node<any>) => { const { props } = node; const stackingContextProps = getStackingContextProps( props as AnimatedProps<DrawingNodeProps> ); recorder.saveGroup(stackingContextProps); const { colorFilters, maskFilters, drawings, shaders, imageFilters, pathEffects, paints, } = sortNodeChildren(node); const paint = processPaint(props); const shouldPushPaint = paint || colorFilters.length > 0 || maskFilters.length > 0 || imageFilters.length > 0 || pathEffects.length > 0 || shaders.length > 0; if (shouldPushPaint) { recorder.savePaint(paint ?? {}, false); pushColorFilters(recorder, colorFilters); pushImageFilters(recorder, imageFilters); pushMaskFilters(recorder, maskFilters); pushShaders(recorder, shaders); pushPathEffects(recorder, pathEffects); // For mixed nodes like BackdropFilters we don't materialize the paint if (node.type === NodeType.BackdropFilter) { recorder.saveBackdropFilter(); } else { recorder.materializePaint(); } } pushPaints(recorder, paints); if (node.type === NodeType.Layer) { recorder.saveLayer(); } const ctm = processCTM(props); const shouldRestore = !!ctm || node.type === NodeType.Layer; if (ctm) { recorder.saveCTM(ctm); } switch (node.type) { case NodeType.Box: const shadows = node.children .filter((n) => n.type === NodeType.BoxShadow) // eslint-disable-next-line @typescript-eslint/no-shadow .map(({ props }) => ({ props }) as { props: BoxShadowProps }); recorder.drawBox(props, shadows); break; case NodeType.Fill: recorder.drawPaint(); break; case NodeType.Image: recorder.drawImage(props); break; case NodeType.Circle: recorder.drawCircle(props); break; case NodeType.Points: recorder.drawPoints(props); break; case NodeType.Path: recorder.drawPath(props); break; case NodeType.Rect: recorder.drawRect(props); break; case NodeType.RRect: recorder.drawRRect(props); break; case NodeType.Oval: recorder.drawOval(props); break; case NodeType.Line: recorder.drawLine(props); break; case NodeType.Patch: recorder.drawPatch(props); break; case NodeType.Vertices: recorder.drawVertices(props); break; case NodeType.DiffRect: recorder.drawDiffRect(props); break; case NodeType.Text: recorder.drawText(props); break; case NodeType.TextPath: recorder.drawTextPath(props); break; case NodeType.TextBlob: recorder.drawTextBlob(props); break; case NodeType.Glyphs: recorder.drawGlyphs(props); break; case NodeType.Picture: recorder.drawPicture(props); break; case NodeType.ImageSVG: recorder.drawImageSVG(props); break; case NodeType.Paragraph: recorder.drawParagraph(props); break; case NodeType.Skottie: recorder.drawSkottie(props); break; case NodeType.Atlas: recorder.drawAtlas(props); break; } drawings.forEach((drawing) => { visitNode(recorder, drawing); }); if (shouldPushPaint) { recorder.restorePaint(); } if (shouldRestore) { recorder.restoreCTM(); } recorder.restoreGroup(); }; export const visit = (recorder: BaseRecorder, root: Node[]) => { root.forEach((node) => { visitNode(recorder, node); }); };