mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
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> ); }; ```  ## 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, }, }); ```  ## 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)
This commit is contained in:
@@ -13,86 +13,13 @@ import {
|
||||
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';
|
||||
|
||||
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,
|
||||
},
|
||||
welcome: {
|
||||
padding: 10,
|
||||
color: '#f60',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
link: {
|
||||
height: 40,
|
||||
alignSelf: 'stretch',
|
||||
width: Dimensions.get('window').width / 2 - 10,
|
||||
},
|
||||
title: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
cell: {
|
||||
height: 40,
|
||||
paddingHorizontal: 10,
|
||||
alignSelf: 'stretch',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
borderTopWidth: hairline,
|
||||
borderTopColor: '#ccc',
|
||||
marginTop: -hairline,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
close: {
|
||||
position: 'absolute',
|
||||
right: 20,
|
||||
top: 40,
|
||||
},
|
||||
scroll: {
|
||||
position: 'absolute',
|
||||
top: 30,
|
||||
right: 10,
|
||||
bottom: 20,
|
||||
left: 10,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
scrollContent: {
|
||||
borderTopWidth: hairline,
|
||||
borderTopColor: '#ccc',
|
||||
},
|
||||
example: {
|
||||
paddingVertical: 25,
|
||||
alignSelf: 'stretch',
|
||||
alignItems: 'center',
|
||||
borderBottomWidth: hairline,
|
||||
borderBottomColor: '#ccc',
|
||||
},
|
||||
sampleTitle: {
|
||||
marginHorizontal: 15,
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
},
|
||||
});
|
||||
import {commonStyles} from './src/commonStyles';
|
||||
|
||||
const names: (keyof typeof examples)[] = [
|
||||
'Svg',
|
||||
@@ -116,6 +43,8 @@ const names: (keyof typeof examples)[] = [
|
||||
'Transforms',
|
||||
'Markers',
|
||||
'Mask',
|
||||
'Filters',
|
||||
'FilterImage',
|
||||
];
|
||||
|
||||
const initialState = {
|
||||
@@ -142,8 +71,8 @@ export default class SvgExample extends Component {
|
||||
content: (
|
||||
<View>
|
||||
{samples.map((Sample, i) => (
|
||||
<View style={styles.example} key={`sample-${i}`}>
|
||||
<Text style={styles.sampleTitle}>{Sample.title}</Text>
|
||||
<View style={commonStyles.example} key={`sample-${i}`}>
|
||||
<Text style={commonStyles.sampleTitle}>{Sample.title}</Text>
|
||||
<Sample />
|
||||
</View>
|
||||
))}
|
||||
@@ -171,9 +100,9 @@ export default class SvgExample extends Component {
|
||||
underlayColor="#ccc"
|
||||
key={`example-${name}`}
|
||||
onPress={() => this.show(name)}>
|
||||
<View style={styles.cell}>
|
||||
<View style={commonStyles.cell}>
|
||||
{icon}
|
||||
<Text style={styles.title}>{name}</Text>
|
||||
<Text style={commonStyles.title}>{name}</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
@@ -182,13 +111,15 @@ export default class SvgExample extends Component {
|
||||
|
||||
modalContent = () => (
|
||||
<>
|
||||
<ScrollView
|
||||
style={styles.scroll}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
scrollEnabled={this.state.scroll}>
|
||||
{this.state.content}
|
||||
</ScrollView>
|
||||
<View style={styles.close}>
|
||||
<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" />
|
||||
@@ -196,14 +127,14 @@ export default class SvgExample extends Component {
|
||||
<Line x1="4" y1="16" x2="16" y2="4" stroke="#fff" strokeWidth="2" />
|
||||
</Svg>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</>
|
||||
);
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>SVG library for React Apps</Text>
|
||||
<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 ? (
|
||||
@@ -217,7 +148,50 @@ export default class SvgExample extends Component {
|
||||
{this.modalContent()}
|
||||
</Modal>
|
||||
)}
|
||||
</View>
|
||||
</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',
|
||||
},
|
||||
});
|
||||
|
||||
BIN
apps/examples/src/assets/office.jpg
Normal file
BIN
apps/examples/src/assets/office.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
41
apps/examples/src/commonStyles.ts
Normal file
41
apps/examples/src/commonStyles.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
const hairline = StyleSheet.hairlineWidth;
|
||||
|
||||
export const commonStyles = StyleSheet.create({
|
||||
welcome: {
|
||||
padding: 10,
|
||||
color: '#f60',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
link: {
|
||||
height: 40,
|
||||
},
|
||||
title: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
cell: {
|
||||
height: 40,
|
||||
paddingHorizontal: 10,
|
||||
alignSelf: 'stretch',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
borderTopWidth: hairline,
|
||||
borderTopColor: '#ccc',
|
||||
marginTop: -hairline,
|
||||
},
|
||||
example: {
|
||||
paddingVertical: 25,
|
||||
alignSelf: 'stretch',
|
||||
alignItems: 'center',
|
||||
borderBottomWidth: hairline,
|
||||
borderBottomColor: '#ccc',
|
||||
},
|
||||
sampleTitle: {
|
||||
marginHorizontal: 15,
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
},
|
||||
});
|
||||
@@ -19,6 +19,8 @@ import * as Reanimated from './examples/Reanimated';
|
||||
import * as Transforms from './examples/Transforms';
|
||||
import * as Markers from './examples/Markers';
|
||||
import * as Mask from './examples/Mask';
|
||||
import * as Filters from './examples/Filters';
|
||||
import * as FilterImage from './examples/FilterImage';
|
||||
|
||||
export {
|
||||
Svg,
|
||||
@@ -42,4 +44,6 @@ export {
|
||||
Transforms,
|
||||
Markers,
|
||||
Mask,
|
||||
Filters,
|
||||
FilterImage,
|
||||
};
|
||||
|
||||
142
apps/examples/src/examples/FilterImage/FilterPicker.tsx
Normal file
142
apps/examples/src/examples/FilterImage/FilterPicker.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import React, {useState} from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {FilterImage, Filters} from 'react-native-svg/filter-image';
|
||||
|
||||
const img = require('../../assets/office.jpg');
|
||||
|
||||
const normal: Filters = [];
|
||||
const losAngeles: Filters = [
|
||||
{
|
||||
name: 'colorMatrix',
|
||||
type: 'matrix',
|
||||
values: [1.8, 0, 0, 0, 0, 0, 1.3, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0],
|
||||
},
|
||||
];
|
||||
const lagos: Filters = [
|
||||
{
|
||||
name: 'colorMatrix',
|
||||
type: 'matrix',
|
||||
values: [
|
||||
1.4, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1.5, 0, 0, 0, 0, 0, 0.9, 0,
|
||||
],
|
||||
},
|
||||
];
|
||||
const tokyo: Filters = [
|
||||
{name: 'colorMatrix', type: 'saturate', values: [1.5]},
|
||||
{
|
||||
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 saturated: Filters = [
|
||||
{name: 'colorMatrix', type: 'saturate', values: [1.5]},
|
||||
];
|
||||
const boring: Filters = [
|
||||
{
|
||||
name: 'colorMatrix',
|
||||
type: 'matrix',
|
||||
values: [
|
||||
0.6965, 0.3845, 0.0945, 0, 0, 0.1745, 0.8430000000000001, 0.084, 0, 0,
|
||||
0.136, 0.267, 0.5655, 0, 0, 0, 0, 0, 1, 0,
|
||||
],
|
||||
},
|
||||
];
|
||||
const filters = {
|
||||
normal,
|
||||
losAngeles,
|
||||
lagos,
|
||||
tokyo,
|
||||
saturated,
|
||||
boring,
|
||||
} as const;
|
||||
|
||||
type FilterKeys =
|
||||
| 'normal'
|
||||
| 'losAngeles'
|
||||
| 'lagos'
|
||||
| 'tokyo'
|
||||
| 'saturated'
|
||||
| 'boring';
|
||||
const filterKeys = Object.keys(filters) as FilterKeys[];
|
||||
const FilterImagePickerExample = () => {
|
||||
const [currentFilter, setCurrentFilter] = useState<FilterKeys>('normal');
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<FilterImage
|
||||
style={styles.image}
|
||||
source={img}
|
||||
filters={filters[currentFilter]}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<FlatList
|
||||
data={filterKeys}
|
||||
horizontal
|
||||
style={styles.list}
|
||||
contentContainerStyle={styles.listElement}
|
||||
renderItem={({item}) => {
|
||||
return (
|
||||
<TouchableOpacity onPress={() => setCurrentFilter(item)}>
|
||||
<FilterImage
|
||||
style={styles.listElementImage}
|
||||
source={img}
|
||||
filters={filters[item]}
|
||||
/>
|
||||
<Text numberOfLines={1} style={styles.listElementTitle}>
|
||||
{item}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
FilterImagePickerExample.title = 'Filter picker';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
height: Dimensions.get('window').height - 150,
|
||||
width: '100%',
|
||||
},
|
||||
image: {flex: 1, width: '100%', height: '100%'},
|
||||
list: {
|
||||
marginTop: 8,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
listElement: {gap: 8},
|
||||
listElementImage: {width: 70, height: 70},
|
||||
listElementTitle: {
|
||||
width: 70,
|
||||
textAlign: 'center',
|
||||
marginTop: 2,
|
||||
marginBottom: 8,
|
||||
},
|
||||
});
|
||||
|
||||
const icon = (
|
||||
<FilterImage
|
||||
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
|
||||
source={img}
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
);
|
||||
|
||||
const samples = [FilterImagePickerExample];
|
||||
export {icon, samples};
|
||||
29
apps/examples/src/examples/FilterImage/LocalImage.tsx
Normal file
29
apps/examples/src/examples/FilterImage/LocalImage.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {FilterImage} from 'react-native-svg/filter-image';
|
||||
|
||||
const testImage = require('../../assets/image.jpg');
|
||||
|
||||
const FilterImageLocalExample = () => {
|
||||
return (
|
||||
<View>
|
||||
<FilterImage
|
||||
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
|
||||
source={testImage}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
FilterImageLocalExample.title = 'Local image with filter';
|
||||
|
||||
const icon = (
|
||||
<FilterImage
|
||||
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
|
||||
source={testImage}
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
);
|
||||
|
||||
const samples = [FilterImageLocalExample];
|
||||
export {icon, samples};
|
||||
52
apps/examples/src/examples/FilterImage/RemoteImage.tsx
Normal file
52
apps/examples/src/examples/FilterImage/RemoteImage.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {FilterImage} from 'react-native-svg/filter-image';
|
||||
|
||||
const testSource = {
|
||||
uri: 'https://cdn.pixabay.com/photo/2023/03/17/11/39/mountain-7858482_1280.jpg',
|
||||
};
|
||||
|
||||
const FilterImageRemoteExample = () => {
|
||||
return (
|
||||
<View>
|
||||
<FilterImage
|
||||
filters={[{name: 'colorMatrix', type: 'saturate', values: [3]}]}
|
||||
source={testSource}
|
||||
style={{width: 200, height: 200}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
FilterImageRemoteExample.title = 'Remote image with filter';
|
||||
|
||||
const FilterImageFewFiltersExample = () => {
|
||||
return (
|
||||
<View>
|
||||
<FilterImage
|
||||
filters={[
|
||||
{name: 'colorMatrix', type: 'saturate', values: [10]},
|
||||
{
|
||||
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',
|
||||
},
|
||||
]}
|
||||
source={testSource}
|
||||
style={{width: 200, height: 200}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
FilterImageFewFiltersExample.title = 'Remote image with filters';
|
||||
|
||||
const icon = (
|
||||
<FilterImage
|
||||
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
|
||||
source={testSource}
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
);
|
||||
|
||||
const samples = [FilterImageRemoteExample, FilterImageFewFiltersExample];
|
||||
export {icon, samples};
|
||||
4
apps/examples/src/examples/FilterImage/examples.tsx
Normal file
4
apps/examples/src/examples/FilterImage/examples.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import * as LocalImage from './LocalImage';
|
||||
import * as RemoteImage from './RemoteImage';
|
||||
import * as FilterPicker from './FilterPicker';
|
||||
export {LocalImage, RemoteImage, FilterPicker};
|
||||
65
apps/examples/src/examples/FilterImage/index.tsx
Normal file
65
apps/examples/src/examples/FilterImage/index.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, TouchableHighlight, View} from 'react-native';
|
||||
|
||||
import {FilterImage} from 'react-native-svg/filter-image';
|
||||
import {commonStyles} from '../../commonStyles';
|
||||
import * as examples from './examples';
|
||||
|
||||
const FilterImageList = () => {
|
||||
const [example, setExample] = React.useState<keyof typeof examples | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
if (example) {
|
||||
return (
|
||||
<>
|
||||
{examples[example].samples.map((Sample, i) => (
|
||||
<View style={commonStyles.example} key={`sample-${i}`}>
|
||||
<Text style={commonStyles.sampleTitle}>{Sample.title}</Text>
|
||||
<Sample />
|
||||
</View>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={commonStyles.welcome}>Filter Image</Text>
|
||||
{Object.keys(examples).map((element, i) => {
|
||||
const name = element as keyof typeof examples;
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.link}
|
||||
underlayColor="#ccc"
|
||||
key={`example-${name}`}
|
||||
onPress={() => setExample(name)}>
|
||||
<View style={commonStyles.cell}>
|
||||
{examples[name].icon}
|
||||
<Text style={commonStyles.title}>{name}</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
FilterImageList.title = '';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {width: '100%'},
|
||||
link: {height: 40},
|
||||
});
|
||||
|
||||
const icon = (
|
||||
<FilterImage
|
||||
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
|
||||
source={require('../../assets/image.jpg')}
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
);
|
||||
|
||||
const samples = [FilterImageList];
|
||||
|
||||
export {icon, samples};
|
||||
130
apps/examples/src/examples/Filters/FeColorMatrix.tsx
Normal file
130
apps/examples/src/examples/Filters/FeColorMatrix.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Svg, Circle, FeColorMatrix, Filter, G} from 'react-native-svg';
|
||||
|
||||
class ReferenceExample extends Component {
|
||||
static title = 'Reference';
|
||||
render() {
|
||||
return (
|
||||
<Svg height="150" width="150">
|
||||
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
class IdentityExample extends Component {
|
||||
static title = 'Identity matrix';
|
||||
render() {
|
||||
return (
|
||||
<Svg height="150" width="150">
|
||||
<Filter id="filter">
|
||||
<FeColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
|
||||
</Filter>
|
||||
<G filter="url(#filter)">
|
||||
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
class RgbToGreenExample extends Component {
|
||||
static title = 'RGB to Green';
|
||||
render() {
|
||||
return (
|
||||
<Svg height="150" width="150">
|
||||
<Filter id="filter">
|
||||
<FeColorMatrix
|
||||
values="0 0 0 0 0
|
||||
1 1 1 1 0
|
||||
0 0 0 0 0
|
||||
0 0 0 1 0"
|
||||
/>
|
||||
</Filter>
|
||||
<G filter="url(#filter)">
|
||||
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
class SaturateExample extends Component {
|
||||
static title = 'Saturate';
|
||||
render() {
|
||||
return (
|
||||
<Svg height="150" width="150">
|
||||
<Filter id="filter">
|
||||
<FeColorMatrix type="saturate" values="0.2" />
|
||||
</Filter>
|
||||
<G filter="url(#filter)">
|
||||
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HueRotateExample extends Component {
|
||||
static title = 'Hue Rotate';
|
||||
render() {
|
||||
return (
|
||||
<Svg height="150" width="150">
|
||||
<Filter id="filter">
|
||||
<FeColorMatrix type="hueRotate" values="180" />
|
||||
</Filter>
|
||||
<G filter="url(#filter)">
|
||||
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LuminanceToAlphaExample extends Component {
|
||||
static title = 'Luminance to alpha';
|
||||
render() {
|
||||
return (
|
||||
<Svg height="150" width="150">
|
||||
<Filter id="filter">
|
||||
<FeColorMatrix type="luminanceToAlpha" />
|
||||
</Filter>
|
||||
<G filter="url(#filter)">
|
||||
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const icon = (
|
||||
<Svg height="30" width="30" viewBox="0 0 20 20">
|
||||
<Filter id="filter">
|
||||
<FeColorMatrix values="0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0" />
|
||||
</Filter>
|
||||
<G filter="url(#filter)">
|
||||
<Circle cx="10" cy="7.5" r="5" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="7.5" cy="12.5" r="5" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="12.5" cy="12.5" r="5" fill="red" fillOpacity="0.5" />
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
const samples = [
|
||||
ReferenceExample,
|
||||
IdentityExample,
|
||||
RgbToGreenExample,
|
||||
SaturateExample,
|
||||
HueRotateExample,
|
||||
LuminanceToAlphaExample,
|
||||
];
|
||||
export {icon, samples};
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import Animated, {
|
||||
AnimatedProps,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
withRepeat,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import {
|
||||
Circle,
|
||||
FeColorMatrix,
|
||||
FeColorMatrixProps,
|
||||
Filter,
|
||||
Image,
|
||||
Svg,
|
||||
} from 'react-native-svg';
|
||||
|
||||
const AnimatedFeColorMatrix = Animated.createAnimatedComponent(
|
||||
FeColorMatrix as any,
|
||||
) as React.FunctionComponent<AnimatedProps<FeColorMatrixProps>>;
|
||||
const ReanimatedHueRotateExample = () => {
|
||||
const hue = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
hue.value = withRepeat(withTiming(360, {duration: 2000}), -1, true);
|
||||
}, []);
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
return {values: [hue.value]};
|
||||
});
|
||||
|
||||
return (
|
||||
<Svg height="100" width="150">
|
||||
<Filter id="filter">
|
||||
<AnimatedFeColorMatrix type="hueRotate" animatedProps={animatedProps} />
|
||||
</Filter>
|
||||
<Image
|
||||
href="https://cdn.pixabay.com/photo/2024/05/26/00/40/lizard-8787888_1280.jpg"
|
||||
height="100"
|
||||
width="150"
|
||||
filter="url(#filter)"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
ReanimatedHueRotateExample.title = 'Reanimated Hue Rotate';
|
||||
|
||||
const icon = (
|
||||
<Svg height="30" width="30" viewBox="0 0 20 20">
|
||||
<Circle cx="10" cy="7.5" r="5" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="7.5" cy="12.5" r="5" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="12.5" cy="12.5" r="5" fill="red" fillOpacity="0.5" />
|
||||
</Svg>
|
||||
);
|
||||
|
||||
const samples = [ReanimatedHueRotateExample];
|
||||
export {icon, samples};
|
||||
3
apps/examples/src/examples/Filters/examples.tsx
Normal file
3
apps/examples/src/examples/Filters/examples.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as FeColorMatrix from './FeColorMatrix';
|
||||
import * as ReanimatedFeColorMatrix from './ReanimatedFeColorMatrix';
|
||||
export {FeColorMatrix, ReanimatedFeColorMatrix};
|
||||
64
apps/examples/src/examples/Filters/index.tsx
Normal file
64
apps/examples/src/examples/Filters/index.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, TouchableHighlight, View} from 'react-native';
|
||||
import {Circle, Svg} from 'react-native-svg';
|
||||
|
||||
import * as examples from './examples';
|
||||
import {commonStyles} from '../../commonStyles';
|
||||
|
||||
const FiltersList = () => {
|
||||
const [example, setExample] = React.useState<keyof typeof examples | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
if (example) {
|
||||
return (
|
||||
<>
|
||||
{examples[example].samples.map((Sample, i) => (
|
||||
<View style={commonStyles.example} key={`sample-${i}`}>
|
||||
<Text style={commonStyles.sampleTitle}>{Sample.title}</Text>
|
||||
<Sample />
|
||||
</View>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={commonStyles.welcome}>SVG Filters</Text>
|
||||
{Object.keys(examples).map((element, i) => {
|
||||
const name = element as keyof typeof examples;
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.link}
|
||||
underlayColor="#ccc"
|
||||
key={`example-${name}`}
|
||||
onPress={() => setExample(name)}>
|
||||
<View style={commonStyles.cell}>
|
||||
{examples[name].icon}
|
||||
<Text style={commonStyles.title}>{name}</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
FiltersList.title = '';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {width: '100%'},
|
||||
link: {height: 40},
|
||||
});
|
||||
|
||||
const icon = (
|
||||
<Svg height="30" width="30" viewBox="0 0 20 20">
|
||||
<Circle cx="10" cy="7.5" r="5" fill="blue" fillOpacity="0.5" />
|
||||
<Circle cx="7.5" cy="12.5" r="5" fill="green" fillOpacity="0.5" />
|
||||
<Circle cx="12.5" cy="12.5" r="5" fill="red" fillOpacity="0.5" />
|
||||
</Svg>
|
||||
);
|
||||
|
||||
const samples = [FiltersList];
|
||||
|
||||
export {icon, samples};
|
||||
Reference in New Issue
Block a user