Files
react-native-svg/apps/examples/App.tsx
Jakub Grzywacz 08e92074b4 feat: filters support FeColorMatrix and FilterImage (#2316)
# Summary

Introducing the long-awaited **Filters** in `react-native-svg` 🎉

### Motivation

This PR is the beginning of bringing support of SVG Filters into
`react-native-svg`.

* **related issues**: This PR series will address the following issues:
#150, #176, #635, #883, #994, #996, #1216
* **feature overview**: This PR is a boilerplate for Filters
   * introducing `Filter` component and `FeColorMatrix` as a start. 
* It also introduces a new subdirectory called
`react-native-svg/filter-image` with a `FilterImage` component.

# Usage

## Filter and Fe...

Filters are compatible with the web familiar standard, so most things
should be compatible out-of-the-box and changes will be limited to using
a capital letter as it's component.

### Example

```tsx
import React from 'react';
import { FeColorMatrix, Filter, Rect, Svg } from 'react-native-svg';

export default () => {
  return (
    <Svg height="300" width="300">
      <Filter id="myFilter">
        <FeColorMatrix type="saturate" values="0.2" />
      </Filter>
      <Rect
        x="0"
        y="0"
        width="300"
        height="300"
        fill="red"
        filter="url(#myFilter)"
      />
    </Svg>
  );
};
```

![image](https://github.com/software-mansion/react-native-svg/assets/39670088/c36fb238-95f4-455d-b0aa-2a7d4038b828)

## Filter Image

`FilterImage` is a new component that is not strictly related to SVG.
Its behavior should be the same as a regular `Image` component from
React Native with one exception - the additional prop `filters`, which
accepts an array of filters to apply to the image.

### Example

```tsx
import React from 'react';
import { StyleSheet } from 'react-native';
import { FilterImage } from 'react-native-svg/filter-image';

const myImage = require('./myImage.jpg');

export default () => {
  return (
    <FilterImage
      style={styles.image}
      source={myImage}
      filters={[
        { name: 'colorMatrix', type: 'saturate', values: 0.2 },
        {
          name: 'colorMatrix',
          type: 'matrix',
          values: [
            0.2, 0.2, 0.2, 0, 0, 0.2, 0.2, 0.2, 0, 0, 0.2, 0.2, 0.2, 0, 0, 0, 0,
            0, 1, 0,
          ],
        },
      ]}
    />
  );
};
const styles = StyleSheet.create({
  image: {
    width: 200,
    height: 200,
  },
});
```


![image](https://github.com/software-mansion/react-native-svg/assets/39670088/666ed89f-68d8-491b-b97f-1eef112b7095)

## Test Plan

**Example App**: Updated the example app with various filter effects,
showcasing real-world usage.

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |         |
| Android |         |

## Checklist

- [x] I have tested this on a device and a simulator
- [x] I added documentation in `README.md` and `USAGE.md`
- [x] I updated the typed files (typescript)
2024-07-11 11:17:35 +02:00

198 lines
4.4 KiB
TypeScript

/**
* Sample React Native App for react-native-svg library
* https://github.com/magicismight/react-native-svg/tree/master/Example
*/
'use strict';
import React, {Component} from 'react';
import {
Dimensions,
StyleSheet,
Text,
View,
ScrollView,
TouchableHighlight,
TouchableOpacity,
SafeAreaView,
} from 'react-native';
import {Modal, Platform} from 'react-native';
import {Svg, Circle, Line} from 'react-native-svg';
import * as examples from './src/examples';
import {commonStyles} from './src/commonStyles';
const names: (keyof typeof examples)[] = [
'Svg',
'Stroking',
'Path',
'Line',
'Rect',
'Polygon',
'Polyline',
'Circle',
'Ellipse',
'G',
'Text',
'Gradients',
'Clipping',
'Image',
'TouchEvents',
'PanResponder',
'Reusable',
'Reanimated',
'Transforms',
'Markers',
'Mask',
'Filters',
'FilterImage',
];
const initialState = {
modal: false,
content: null,
};
export default class SvgExample extends Component {
state: {
content: React.ReactNode;
modal: boolean;
scroll?: boolean;
} = initialState;
show = (name: keyof typeof examples) => {
if (this.state.modal) {
return;
}
let example = examples[name];
if (example) {
let samples = example.samples;
this.setState({
modal: true,
content: (
<View>
{samples.map((Sample, i) => (
<View style={commonStyles.example} key={`sample-${i}`}>
<Text style={commonStyles.sampleTitle}>{Sample.title}</Text>
<Sample />
</View>
))}
</View>
),
scroll: (example as {scroll?: boolean}).scroll !== false,
});
}
};
hide = () => {
this.setState(initialState);
};
getExamples = () => {
return names.map(name => {
var icon;
let example = examples[name];
if (example) {
icon = example.icon;
}
return (
<TouchableHighlight
style={styles.link}
underlayColor="#ccc"
key={`example-${name}`}
onPress={() => this.show(name)}>
<View style={commonStyles.cell}>
{icon}
<Text style={commonStyles.title}>{name}</Text>
</View>
</TouchableHighlight>
);
});
};
modalContent = () => (
<>
<SafeAreaView style={{flex: 1}}>
<ScrollView
style={styles.scroll}
contentContainerStyle={styles.scrollContent}
scrollEnabled={this.state.scroll}>
{this.state.content}
</ScrollView>
</SafeAreaView>
<SafeAreaView style={styles.close}>
<TouchableOpacity activeOpacity={0.7} onPress={this.hide}>
<Svg height="20" width="20">
<Circle cx="10" cy="10" r="10" fill="red" />
<Line x1="4" y1="4" x2="16" y2="16" stroke="#fff" strokeWidth="2" />
<Line x1="4" y1="16" x2="16" y2="4" stroke="#fff" strokeWidth="2" />
</Svg>
</TouchableOpacity>
</SafeAreaView>
</>
);
render() {
return (
<SafeAreaView style={styles.container}>
<Text style={commonStyles.welcome}>SVG library for React Apps</Text>
<View style={styles.contentContainer}>{this.getExamples()}</View>
{(Platform.OS === 'windows' || Platform.OS === 'macos') &&
this.state.modal ? (
<View style={styles.scroll}>{this.modalContent()}</View>
) : (
<Modal
transparent={false}
animationType="fade"
visible={this.state.modal}
onRequestClose={this.hide}>
{this.modalContent()}
</Modal>
)}
</SafeAreaView>
);
}
}
const hairline = StyleSheet.hairlineWidth;
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
alignItems: 'center',
overflow: 'hidden',
},
contentContainer: {
alignSelf: 'stretch',
borderTopWidth: hairline,
borderTopColor: '#ccc',
borderBottomWidth: hairline,
borderBottomColor: '#ccc',
flexWrap: 'wrap',
flexDirection: 'row',
marginHorizontal: 10,
},
link: {
height: 40,
alignSelf: 'stretch',
width: Dimensions.get('window').width / 2 - 10,
},
close: {
position: 'absolute',
right: 20,
top: 20,
},
scroll: {
position: 'absolute',
top: 30,
right: 10,
bottom: 20,
left: 10,
backgroundColor: '#fff',
},
scrollContent: {
borderTopWidth: hairline,
borderTopColor: '#ccc',
},
});