Create high-performance graphics with React Native Skia
Tags: reactnative
Published: 2022-04-21 15:45:18

**Written by [Rupesh Chaudhari](https://blog.logrocket.com/author/rupeshchaudhari/)✏️**

[Skia](https://skia.org/) is a popular open-source 2D graphics library used by major platforms like Google Chrome, Chrome OS, Android, and Flutter as their default graphics engine. The library is sponsored and managed by Google, while the development is overseen by Skia’s engineering team.

## What is React Native Skia?

Thanks to [Shopify](https://github.com/Shopify), [William Candollin](https://github.com/wcandillon), [Christian Falch](https://github.com/chrfalch), and the entire dev team behind [react-native-skia](https://github.com/shopify/react-native-skia), we can now use [Skia](https://skia.org/) in our React Native applications to draw awesome graphics and create trendy UI concepts like `Neumorphism` and `Glassmorphism`.

> Note: The library is still in alpha stage and is not yet available on npm, thus, it is not completely stable yet.

You can find the complete code for this article in this GitHub [repository](https://github.com/hrupesh/react-native-skia-demo).

## Installing React Native Skia

Let’s get started with library installation in a bare React Native project. Because the library is not yet published on npm, we need to download it from their GitHub repository.

Let’s start by initializing a new react native project.

```js
react-native init <project-name>
```

After we set our project up, let’s add React Native Skia.

Run the following in your terminal:

```js
yarn add https://github.com/Shopify/react-native-skia/releases/download/v0.1.106-alpha/shopify-react-native-skia-0.1.106.tgz
```

Or, if you use npm:

```js
npm install https://github.com/Shopify/react-native-skia/releases/download/v0.1.106-alpha/shopify-react-native-skia-0.1.106.tgz
```

At the time of writing, the latest alpha release version is `0.1.106`, which we’ll use in this tutorial.

> Note: once newer versions are released, there will likely be some changes and new features.

Once the package is downloaded and installed, we need to set up the project in our native iOS and Android projects.

For iOS, this is pretty straightforward — we just need to install the cocoa pods dependency. Use this command to do so:

```js
cd ios
pod install
```

Now, we need to rebuild our iOS project so that we can use `Skia` in our React Native iOS projects.

Build the project from the terminal or directly from Xcode:

```js
yarn ios
// OR
npm run ios
```

Setting up our project in Android can be a bit complex if you’re not familiar with the environment. Because most of the graphic rendering logic for React Native Skia is written in `C++`, we will need something known as `NDK` (Android [Native Development Kit](https://developer.android.com/ndk)) for the communication between Java code and C++ code.

> “The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android” - [Android Documentation](https://developer.android.com/ndk/guides)

To install Android NDK, you can follow these steps from the [official Android docs](https://developer.android.com/studio/projects/install-ndk). Now that we set our library up in both iOS and Android, we can now begin creating a beautiful user interface and graphics.

## Using React Native Skia in a React Native app

React Native Skia offers two types of APIs to draw graphics:

*   A declarative API, which uses the default React Native renderer
*   An imperative API, which works with JSI (JavaScript Interface)

> React Native JSI is a core change in the re-architecture of React Native. This is a layer that offers “synchronous” communication between JavaScript and native code. It will be replacing the default react native bridge. ([Source](https://github.com/react-native-community/discussions-and-proposals/issues/91))

The declarative API is similar to how we currently create UI in React Native using JSX syntax. If we want to create a rectangular shape with rounded corners, then the syntax for it will look like this:

```js
import React from 'react';
import { Canvas, Fill, RoundedRect, vec } from '@shopify/react-native-skia';
import { StatusBar, useWindowDimensions } from 'react-native';

export const DeclarativeAPI = () => {
  const {width, height} = useWindowDimensions();
  const center = vec(width / 2, height / 2);
  return (
    <>
      <StatusBar barStyle="light-content" />
      <Canvas style={{flex: 1}}>
        <Fill color={'#222'} />
        <RoundedRect 
          height={180}
          width={300}
          x={center.x - 150}
          y={center.y - 90}
          color={'#eee'}
          rx={12}
        />
      </Canvas>
    </>
  );
};
```

The output from the above code will be:

![White Square](https://blog.logrocket.com/wp-content/uploads/2022/04/white-square.png)

The library owners prefer users to use the declarative API, as it is easy to use and the code is readable.

Here’s what we have done in the above code. First, we needed a canvas to draw graphics on, so we wrapped our components with a `Canvas` component and gave it a style of `flex:1` so that it takes up the entire screen space. Then, we used the `Fill` component to fill our `Canvas` with color `#222`.

We also declared a variable `center`, which is `vector` with two keys, X and Y. Finally, to draw our `Rectangle` shape with rounded corners, we used the `RoundedRect` component and gave it below props:

*   Height: `180`, to give the rectangle a height of 180 pixels
*   Width: `300`, to give the rectangle a width of 300 pixels
*   `X = center.x`: `150`, which is the position of the rectangle from the X-axis. In React Native Skia, the X and Y positions start from the top left section
*   `Y = center.y`: `90`, this is the position of the rectangle from the Y-axis
*   `color = #eee`: gives our rectangle a white color
*   `rx = 12`: gives our rectangle a corner radius of `12`

Now, let’s see how we can create the same UI using the imperative API, which looks like this:

```js
import React from 'react';
import {
    rect, rrect, Skia,
    SkiaView,
    useDrawCallback, vec
} from '@shopify/react-native-skia';
import { StatusBar, useWindowDimensions } from 'react-native';

const paint = Skia.Paint();
paint.setAntiAlias(true);

export const ImperativeAPI = () => {
  const {width, height} = useWindowDimensions();
  const center = vec(width / 2, height / 2);
  const rRect = rrect(rect(center.x - 150, center.y - 90, 300, 180), 12, 12);
  const onDraw = useDrawCallback(canvas => {
    const white = paint.copy();
    white.setColor(Skia.Color('#eee'));
    canvas.drawRRect(rRect, white);
  }, []);
  return (
    <>
      <StatusBar barStyle="light-content" />
      <SkiaView style={{flex: 1, backgroundColor: '#222'}} onDraw={onDraw} />
    </>
  );
};
```

The output for the above is identical to the one created by the declarative API.

![White Square](https://blog.logrocket.com/wp-content/uploads/2022/04/white-square-2.png)

Here, we created a `SkiaView`, which is essentially our `Canvas`, and we are using its `onDraw` callback to define what to draw on the view. Then, there is the `onDraw` method, which uses the `useDrawCallback` hook and returns a memoized callback. In the function, we are initializing `Paint`, then using that paint as the color of our rounded rectangle that we drew on the canvas.

## Adding filters to images in React Native Skia

Now, let’s see how you can display an image using Skia’s `Image` component, as well as how to add cool filters to the image in real time.

Let’s write code to display the image first.

```js
import {Canvas, Image, useImage} from '@shopify/react-native-skia';
import React from 'react';
import {Dimensions, SafeAreaView, StyleSheet, Text} from 'react-native';

const {height, width} = Dimensions.get('window');

export const ImageFilters = () => {
  const image = useImage(require('../assets/image.jpg'));
  if (image === null) return null;
  return (
    <SafeAreaView>
      <Text style={styles.title}>Image Filters</Text>
      <Canvas style={styles.canvas}>
        <Image
          x={0}
          y={0}
          width={width - 32}
          height={height - 300}
          image={image}
          fit="cover"
        />
      </Canvas>
    </SafeAreaView>
  );
};
const styles = StyleSheet.create({
  title: {
    paddingVertical: 16,
    paddingHorizontal: 16,
    fontWeight: '700',
    fontSize: 32,
    letterSpacing: 1.4,
  },
  canvas: {
    height: height - 300,
    marginHorizontal: 16,
    width: width - 32,
  },
});
```

In the above code, we initialize the `image` variable using the [`useImage`](https://shopify.github.io/react-native-skia/docs/images) hook from `react-native-skia`.

We can use this hook to initialize local image assets with the help of [`require`](https://reactnative.dev/docs/images#static-image-resources) helper function, or we can also use remote images from the network, like this:

```js
const image = useImage('https://picsum.photos/1920/1080');
```

Then, we wrap the `Image` component from `Skia` in a `Canvas` and add styles to the components. The output of the above code will be:

![Image Filters](https://blog.logrocket.com/wp-content/uploads/2022/04/image-filters.png)

Now that our image is visible, let’s add some filters to it using the [`color matrix`](https://shopify.github.io/react-native-skia/docs/color-filters/#color-matrix), which is a matrix (2D-Array) with values for RGBA colors. The syntax is something like this:

```js
| R' |   | a00 a01 a02 a03 a04 |   | R |
| G' |   | a10 a11 a22 a33 a44 |   | G |
| B' | = | a20 a21 a22 a33 a44 | * | B |
| A' |   | a30 a31 a22 a33 a44 |   | A |
```

We will use the [`ColorMatrix`](https://shopify.github.io/react-native-skia/docs/color-filters#color-matrix) filter component from React Native Skia to achieve this.

First, let’s create matrices for all the filters we will be using:

```js
const filters = {
    Juno: [
        1, 0, 0, 0, 0,
        -0.4, 1.3, -0.4, 0.2, -0.1,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
    ],
    Sepia: [
        0.393, 0.769, 0.189, 0, 0,
        0.349, 0.686, 0.168, 0, 0,
        0.272, 0.534, 0.131, 0, 0,
        0,     0,     0,     1, 0,
    ],
    Greyscale: [
        0.2126, 0.7152, 0.0722, 0, 0,
        0.2126, 0.7152, 0.0722, 0, 0,
        0.2126, 0.7152, 0.0722, 0, 0,
        0,      0,      0,      1, 0,
    ],
    Gingham: [
        2, 0, 0, 0, 0,
        1, 1, 0, 0, 0,
        0.5, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
    ],
    Mayfair: [
        1, 1, 0.5, 0, 0,
        0, 0.5, 1, 0, 0,
        0.5, 0.5, 1, 0, 0,
        0, 0, 0, 1, 0,
    ],
    Valencia: [
        1, 0, 0, 0, 0,
        -0.2, 1, 0, 0, 0,
        -0.8, 1.6, 1, 0, 0,
        0, 0, 0, 1, 0,
    ],
    'No Filter': [
        1, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
    ]
};
```

You can also create your own custom color matrix, [there are many playgrounds available](https://kazzkiq.github.io/svg-color-filter/). Note that we created an object with seven keys, each pointing to a `ColorMatrix` value.

After adding some code to handle our UI, the final code for this will be:

```js
import {
  Canvas,
  ColorMatrix,
  Image,
  Paint,
  useImage,
} from '@shopify/react-native-skia';
import React, {useCallback, useState} from 'react';
import {
  Dimensions,
  FlatList,
  SafeAreaView,
  StyleSheet,
  Text,
} from 'react-native';

const {height, width} = Dimensions.get('window');
const filters = {
    Juno : [
        1, 0, 0, 0, 0,
        -0.4, 1.3, -0.4, 0.2, -0.1,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
    ],
    Sepia: [
        0.393, 0.769, 0.189, 0, 0,
        0.349, 0.686, 0.168, 0, 0,
        0.272, 0.534, 0.131, 0, 0,
        0,     0,     0,     1, 0,
    ],
    Greyscale: [
        0.2126, 0.7152, 0.0722, 0, 0,
        0.2126, 0.7152, 0.0722, 0, 0,
        0.2126, 0.7152, 0.0722, 0, 0,
        0,      0,      0,      1, 0,
    ],
    Gingham: [
        2, 0, 0, 0, 0,
        1, 1, 0, 0, 0,
        0.5, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
    ],
    Mayfair: [
        1, 1, 0.5, 0, 0,
        0, 0.5, 1, 0, 0,
        0.5, 0.5, 1, 0, 0,
        0, 0, 0, 1, 0,
    ],
    Valencia: [
        1, 0, 0, 0, 0,
        -0.2, 1, 0, 0, 0,
        -0.8, 1.6, 1, 0, 0,
        0, 0, 0, 1, 0,
    ],
    ['No Filter']: [
        1, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
    ]
};

export const ImageFilters = () => {
  const [selectedFilter, setSelectedFilter] = useState('Juno');
  const handlePress = useCallback(
    item => () => {
      setSelectedFilter(item);
    },
    [],
  );
  const image = useImage(require('../assets/image.jpg'));
  if (image === null) return null;
  return (
    <SafeAreaView>
      <Text style={styles.title}>Image Filters</Text>
      <Canvas style={styles.canvas}>
        <Paint>
            <ColorMatrix 
              matrix={filters[selectedFilter]}
            />
        </Paint>
        <Image
          x={0}
          y={0}
          width={width - 32}
          height={height - 300}
          image={image}
          fit="cover"
        />
      </Canvas>
      <FlatList
        numColumns={3}
        data={Object.keys(filters)}
        keyExtractor={(_, index) => index}
        renderItem={({item}) => (
          <Text
            style={[
              styles.item,
              selectedFilter === item && styles.selectedItem,
            ]}
            onPress={handlePress(item)}>
            {item}
          </Text>
        )}
        style={styles.list}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  title: {
    paddingVertical: 16,
    paddingHorizontal: 16,
    fontWeight: '700',
    fontSize: 32,
    letterSpacing: 1.4,
    color: '#000',
  },
  canvas: {
    height: height - 300,
    marginHorizontal: 16,
    width: width - 32,
    borderRadius: 12,
  },
  list: {
    margin: 16,
  },
  item: {
      width: '33%',
      textAlign: 'center',
      marginBottom: 12,
      fontWeight: '600',
      fontSize: 18,
  },
  selectedItem: {
      color: '#ea4c89',
      borderWidth: 1,
      borderColor: '#ea4c89',
      borderRadius: 12,
 },
});
```

The output from the above code in iOS and Android are:

![Choosing Different Filters](https://blog.logrocket.com/wp-content/uploads/2022/04/choosing-different-filters.gif)

![Choosing More Filters](https://blog.logrocket.com/wp-content/uploads/2022/04/choosing-more-filters.gif)

That’s it! We’ve now added filters to our images in React Native.

## Creating neumorphic UI with React Native Skia

With `Skia`, we can create neumorphic UI in React Native. React Native Skia provides a [`DropShadow`](https://shopify.github.io/react-native-skia/docs/image-filters/shadows#drop-shadow) component for creating shadows, similar to how we would in web apps to create a neumorphic design pattern.

Below is the code to create a neumorphic list:

```js
import {
  Canvas,
  DropShadow,
  Group,
  Paint,
  RoundedRect,
  Text,
  useFont,
} from '@shopify/react-native-skia';
import React from 'react';
import {Dimensions, ScrollView} from 'react-native';

const data = [
  {
    id: 1,
    name: 'John Doe',
    body: 'Sunt aliqua eu officia eiusmod non',
  },
  {
    id: 2,
    name: 'Jane Doe',
    body: 'Sunt aliqua eu officia eiusmod non',
  },
  {
    id: 3,
    name: 'Erin Yeager',
    body: 'Adipisicing sint ullamco irure nulla',
  },
  {
    id: 4,
    name: 'Mikasa Ackerman',
    body: 'Adipisicing sint ullamco irure nulla',
  },
  {
    id: 5,
    name: 'John Doe',
    body: 'Sunt aliqua eu officia eiusmod non',
  },
  {
    id: 6,
    name: 'Jane Doe',
    body: 'Sunt aliqua eu officia eiusmod non',
  },
  {
    id: 7,
    name: 'John Doe',
    body: 'Sunt aliqua eu officia eiusmod non',
  },
  {
    id: 8,
    name: 'Jane Doe',
    body: 'Sunt aliqua eu officia eiusmod non',
  },
];

const {width} = Dimensions.get('window');

const NeumorphicCard = ({data, index, font, bodyFont}) => {
  return (
    <Canvas style={{height: 140, width}} key={index}>
      <Group>
        <Paint>
          <DropShadow dx={6} dy={6} blur={4} color="rgba(0, 0, 0, 0.4)" />
          <DropShadow dx={-6} dy={-6} blur={4} color="rgba(255, 255, 255, 1)" />
        </Paint>
        <RoundedRect
          x={16}
          y={20}
          width={width - 32}
          height={100}
          rx={12}
          color="#EFEEEE">
          <Paint
            color="rgba(255, 255, 255, 0.6)"
            style="stroke"
            strokeWidth={1}
          />
        </RoundedRect>
      </Group>
      <Text x={32} y={56} text={data?.name} font={font} />
      <Text x={32} y={92} text={data?.body} font={bodyFont} />
    </Canvas>
  );
};

export const Neumorphism = () => {
  const font = useFont(require('../fonts/Poppins-Regular.ttf'), 20);
  const bodyFont = useFont(require('../fonts/Poppins-Regular.ttf'), 14);
  if (font === null || bodyFont === null) {
    return null;
  }
  return (
    <ScrollView
      showsVerticalScrollIndicator={false}
      style={{flex: 1, backgroundColor: '#EFEEEE'}}
      contentContainerStyle={{
        paddingTop: Platform.OS === 'ios' ? 40 : 0,
        paddingBottom: 40,
      }}>
      {data.map(item => (
        <NeumorphicCard
          key={item.id}
          data={item}
          font={font}
          bodyFont={bodyFont}
        />
      ))}
    </ScrollView>
  );
};
```

Above, we are mapping through an array of random data, and, for each item, we are rendering a `NeumorphicCard` component. We are also using the `useFont` hook to load custom fonts.

In the card component, we have a `RoundedRect` with a border of `1` unit, which we have created using `Paint` with the style of `stroke`. Then, we added two `DropShadow` components to create a shadow on the top-left and bottom-right side of the rectangle.

Here is the output of the above code in iOS and Android:

![Drag Scroll](https://blog.logrocket.com/wp-content/uploads/2022/04/drag-scroll.gif)

![Drag Scroll on Android](https://blog.logrocket.com/wp-content/uploads/2022/04/drag-scroll-android.gif)

## Incorporating glassmorphism with React Native Skia.

Finally, let’s create a [glassmorphism](https://hype4.academy/tools/glassmorphism-generator)-inspired design in our React Native app. To create this glassmorphic design, we will need the [`BackDropBlur`](https://shopify.github.io/react-native-skia/docs/backdrops-filters#backdrop-blur) component. We will create a `RoundedRect`, then give the background a `blur` using the `BackDropBlur` component. We will also see how we can animate graphics.

Write the below code to create an animated glassmorphic card:

```js
import {
  BackdropBlur, Canvas, Fill, Group, Image, mix,
  Paint, rect, Rect, RoundedRect, rrect, Text,
  useDerivedValue, useFont, useImage, useLoop, vec,
} from '@shopify/react-native-skia';
import React from 'react';
import {useWindowDimensions} from 'react-native';

export const Glassmorphism = () => {
  const {width, height} = useWindowDimensions();
  const center = vec(width / 2 - 50, height / 2 - 100);
  const blurClipPath = rrect(rect(24, center.y, width - 48, 200), 12, 12);
  const image = useImage('https://picsum.photos/1920/1080');
  const blurProgress = useLoop({duration: 2000});
  const blur = useDerivedValue(
    progress => mix(progress, 0, 10),
    [blurProgress],
  );
  const font = useFont(require('../fonts/Poppins-Regular.ttf'), 40);
  if (font === null) {
    return null;
  }
  return (
    <Canvas style={{flex: 1}}>
      <Group>
        <Rect x={0} y={0} width={width} height={height} />
        <Image
          x={0}
          y={0}
          width={width}
          height={height}
          image={image}
          fit={'cover'}
        />
      </Group>
      <Group>
        <RoundedRect
          x={24}
          y={center.y}
          width={width - 48}
          height={200}
          color="#0000"
          rx={12}>
          <Paint
            color="rgba(255, 255, 255, 0.8)"
            style="stroke"
            strokeWidth={1}
          />
        </RoundedRect>
        <BackdropBlur blur={blur} clip={blurClipPath}>
          <Fill color={'rgba(122, 122, 122, 0.2)'} />
        </BackdropBlur>
        <Text
          x={center.x - 50}
          y={center.y + 110}
          text="Hello Skia"
          font={font}
          color="#fff"
          style="stroke"
        />
      </Group>
    </Canvas>
  );
};
```

The output will look like this:

![Hello Skia in Square](https://blog.logrocket.com/wp-content/uploads/2022/04/hello-skia-square.gif)

## Conclusion

Now you’ve seen just how powerful React Native Skia is, and I think it will change how we create UI in React Native. Because the library uses the full potential of the new React Native architecture (JSI) and the new renderer, it will surely increase our React Native application’s performance. Thanks for reading!

---
##[LogRocket](https://www2.logrocket.com/mobile): Instantly recreate issues in your React Native apps.

![LogRocket React Native monitoring solution](https://blog.logrocket.com/wp-content/uploads/2021/10/react-native-plug_v2-2.png)

[LogRocket](https://www2.logrocket.com/mobile) is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your React Native apps — [try LogRocket for free](https://www2.logrocket.com/mobile).