Skia — высокопроизводительная 2D графика для React Native

—

от автора

Пользователям Flutter не понаслышке знаком такой проект как Skia. Он является движком для рендеринга всего что мы видим на экране Flutter. С помощью него можно рисовать сложные элементы интерфейса и любые 2D сцены с поддержкой плавной анимации и различных эффектов. Так почему бы не взять это на вооружение, подумали ребята из Shopify и выпустили React Native Skia — библиотеку позволяющую использовать Skia в экосистеме React Native.

Для того чтобы посмотреть на что способна Skia предлагаю использовать example из репозитория библиотеки.

Примеры интерфейсов созданных с помощью React Native Skia
Примеры интерфейсов созданных с помощью React Native Skia

Графика

Начнем с базовой графики.

import React from 'react'; import { Group, Rect, RoundedRect, DiffRect, Canvas, rrect } from '@shopify/react-native-skia'; import { StatusBar, useWindowDimensions } from 'react-native';  const PADDING = 16;  export const Declarative = () => {   const { width } = useWindowDimensions();   const SIZE = width / 4;   const style = useMemo(() => ({ width, height: SIZE + 32 }), [SIZE, width]);   const outer = useMemo(     () => rrect(rect(2 * SIZE + 3 * 16, PADDING, SIZE, SIZE), 25, 25),     [SIZE]   );   const inner = useMemo(     () =>       rrect(         rect(2 * SIZE + 4 * PADDING, 2 * PADDING, SIZE - 32, SIZE - 32),         0,         0       ),     [SIZE]   );   return (     <>       <Canvas style={style}>         <Group color="#61DAFB">           <Rect rect={{ x: PADDING, y: PADDING, width: 100, height: 100 }} />           <RoundedRect             x={SIZE + 2 * PADDING}             y={PADDING}             width={SIZE}             height={SIZE}             r={25}           />           <DiffRect outer={outer} inner={inner} />         </Group>       </Canvas>     </>   ); };

Canvas — это корневой элемент для рисования с помощью Skia. К Canvas применяются стили как и к компоненту View с помощью свойства style. Помимо этого с помощью Canvas обрабатываются touch события передав функцию в свойство onTouch.

const MyComponent = () => {   const cx = useValue(100);   const cy = useValue(100);     const touchHandler = useTouchHandler({     onActive: ({ x, y }) => {       cx.current = x;       cy.current = y;     },   });     return (     <Canvas onTouch={touchHandler}>       <Circle cx={cx} cy={cy} r={10} color="red" />     </Canvas>   ); };

Компоненты React, RoundedRect, Circle, DiffRect, Line, Point и прочие фигуры используются для рисования фигур. Каждый компонент имеет свои специфичные свойства и свойства общие для любых компонентов которые мы рисуем с помощью Skia такие, как color, blendMode, style и т.д. Очень полезным может быть использование компонента Group который позволяет применять общие свойства всем дочерним компонентам.

export const PaintDemo = () => {   const r = 128;   return (     <Canvas style={{ flex: 1 }}>       <Circle cx={r} cy={r} r={r} color="#51AFED" />       <Group color="lightblue" style="stroke" strokeWidth={10}>         <Circle cx={r} cy={r} r={r / 2} />         <Circle cx={r} cy={r} r={r / 3} color="white" />       </Group>     </Canvas>   ); };

Продолжая тему рисования — ко всем фигурам мы можем применять различные маски, эффекты, фильтры и трансформации. Например BlurMask

const MaskFilterDemo = () => {   return (     <Canvas style={{ flex: 1}}>       <Circle c={vec(128)} r={128} color="lightblue">         <BlurMask blur={20} style="normal" />       </Circle>     </Canvas>   ); };
const SimpleTransform = () => {   return (     <Canvas style={{ flex: 1 }}>       <Fill color="#e8f4f8" />       <Group         color="lightblue"         origin={{ x: 128, y: 128 }}         transform={[{ skewX: Math.PI / 6 }]}       >         <RoundedRect x={64} y={64} width={128} height={128} r={10} />       </Group>     </Canvas>   ); };

Помимо рисования Skia поддерживает работы с изображениями и SVG. Изображения используются как отдельный компонент или могут быть вписаны в другие фигуры.

const Clip = () => {   const image = useImage(require("./assets/oslo.jpg"));   const star = Skia.Path.MakeFromSVGString(     "M 128 0 L 168 80 L 256 93 L 192 155 L 207 244 L 128 202 L 49 244 L 64 155 L 0 93 L 88 80 L 128 0 Z"   )!;   if (!image) {     return null;   }   return (     <Canvas style={{ flex: 1 }}>       <Group clip={star}>         <Image           image={image}           x={0}           y={0}           width={256}           height={256}           fit="cover"         />       </Group>     </Canvas>   ); };

Отдельно стоит упомянуть поддержку шейдорв. В Skia реализован свой язык похожий на GLSL. Пример использования простейшего шейдера:

import {Skia, Canvas, Shader, Fill} from "@shopify/react-native-skia";   const source = Skia.RuntimeEffect.Make(` vec4 main(vec2 pos) {   // normalized x,y values go from 0 to 1, the canvas is 256x256   vec2 normalized = pos/vec2(256);   return vec4(normalized.x, normalized.y, 0.5, 1); }`)!;   const SimpleShader = () => {   return (     <Canvas style={{ width: 256, height: 256 }}>       <Fill>         <Shader source={source} />       </Fill>     </Canvas>   ); };

Анимация

Поддержка анимаций строиться на концепции Skia Values. С помощью Value мы храним состояние, которое может быть ассоциировано с объектом на Canvas и при изменении этого состояние объект будет перерисован. В качестве значения могут быть использованы строки, числа, объекты и массивы.

const MyComponent = () => {   const position = useValue(0);   const updateValue = useCallback(     () => (position.current = position.current + 10),     [position]   );     return (     <>       <Canvas style={{ flex: 1 }}>         <Rect x={position} y={100} width={10} height={10} color={"red"} />       </Canvas>       <Button title="Move it" onPress={updateValue} />     </>   ); };

С помощью хука useComputedValue можно рассчитать новое значения основываясь на другие values, а с помощью useValueEffect реагировать на изменения values. Для работы со сложными объектами или массивами нужно использовать функцию Selector которая принимаем на вход value и возвращает значение которое используется в конкретном свойстве объекта на Canvas.

const Heights = new Array(10).fill(0).map((_, i) => i * 0.1);   export const Demo = () => {   const loop = useLoop();   const heights = useComputedValue(     () => Heights.map((_, i) => loop.current * i * 10),     [loop]   );     return (     <Canvas style={{ flex: 1, marginTop: 50 }}>       {Heights.map((_, i) => (         <Rect           key={i}           x={i * 20}           y={0}           width={16}           height={Selector(heights, (v) => v[i])}           color="red"         />       ))}     </Canvas>   ); };

Для облегчения работы с values фреймворк поставляется с набором хуков таких как useTiming, useLoop, useSpring и функций interpolate, interpolatePaths, interpolateColors, runDecay для построение анимаций. Пример использования хуков

export const AnimationExample = () => {   const [toggled, setToggled] = useState(false);   const position = useSpring(toggled ? 100 : 0);   return (     <>       <Canvas style={{ flex: 1 }}>         <Rect x={position} y={100} width={10} height={10} color={"red"} />       </Canvas>       <Button title="Toggle" onPress={() => setToggled((p) => !p)} />     </>   ); };

Skia API

Кроме работы в декларативном стиле библиотека так же предлагает доступ к API Skia напрямую используя новые возможности React Native по синхронной коммуникации с нативным кодом (JSI). Это API практически на 100% совместимо с Flutter API. Пример использования.

import {Skia, SkiaView, useDrawCallback} from "@shopify/react-native-skia";   export const HelloWorld = () => {   const r = 128;   const onDraw = useDrawCallback((canvas) => {     const paint = Skia.Paint();     paint.setAntiAlias(true);     cyan.setColor(Skia.Color("cyan"));     canvas.drawCircle(r, r, r, paint);   });   return (     <SkiaView style={{ flex: 1 }} onDraw={onDraw} />   ); };

Выводы

Как видно из примеров в начале статьи фреймворк позволяет использовать много возможностей Skia для проектирования сложных интерфейсов. Поддержка JSI гарантирует минимальный оверхед при работе с нативным движком, это позволит добиться высокой производительности. Из плюсов отмечу поддержку от Shopify, проект скорее всего не будет заброшен и будет активно развиваться.

Из минусов неполная поддержка API Skia и статус Alpha версии библиотеки, API меняется от версии к версии, а частые релизы приносят баги и ломают обратную совместимость.

Подробнее о возможностях React Native Skia читайте в документации https://shopify.github.io/react-native-skia/ а примеры использования на канале одного из разработчиков William Candillon


ссылка на оригинал статьи https://habr.com/ru/articles/685524/