feat: introduce CSS filter API on FilterImage (#2359)

# Summary

Provide a **CSS**-like filter API in the `FilterImage` component. 
Unlike the SVG filter API, which can be complex and challenging even for
simple tasks, the CSS API is straightforward and allows users to quickly
achieve satisfactory results.

The full API can be viewed here
https://developer.mozilla.org/en-US/docs/Web/CSS/filter
_We support all `<filter-function>` listed in the docs while we do not
support functions from `<url>` (`url()` and `src()`)._

All shorthands are implemented according to the W3 standard described
here
https://www.w3.org/TR/filter-effects-1/#ShorthandEquivalents

This PR also changes the `filters` prop behavior as it adds `fe` in
front of `name` attribute to not introduce any abstract above that
specified in the docs.
```tsx
<FilterImage
  source={myImage}
  filters={[{ name: 'colorMatrix', type: 'saturate', values: 0.2 }])
/>
```
is now 
```tsx
<FilterImage
  source={myImage}
  filters={[{ name: 'feColorMatrix', type: 'saturate', values: 0.2 }])
/>
```

## Examples
Below are the simple examples of the usage

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

export default () => {
  return (
    <FilterImage
      style={styles.image}
      source={{
        uri: 'https://cdn.pixabay.com/photo/2024/05/26/00/40/lizard-8787888_1280.jpg',
      }}
    />
  );
};
const styles = StyleSheet.create({
  image: {
    width: 200,
    height: 200,
    filter: 'saturate(100) grayscale(100%)',
  },
});
```

or directly in the `style` prop

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

export default () => {
  return (
    <FilterImage
      style={{
        width: 200,
        height: 200,
        filter: 'saturate(100) grayscale(100%)',
      }}
      source={{
        uri: 'https://cdn.pixabay.com/photo/2024/05/26/00/40/lizard-8787888_1280.jpg',
      }}
    />
  );
};
```

## Compatibility

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

## Checklist

- [x] I have tested this on a device and a simulator
- [x] I added documentation in `README.md`
- [x] I updated the typed files (typescript)
This commit is contained in:
Jakub Grzywacz
2024-07-25 10:19:47 +02:00
committed by GitHub
parent 00e492e8cf
commit 52466a2564
14 changed files with 2508 additions and 57 deletions
@@ -14,14 +14,14 @@ const img = require('../../assets/office.jpg');
const normal: Filters = [];
const losAngeles: Filters = [
{
name: 'colorMatrix',
name: 'feColorMatrix',
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',
name: 'feColorMatrix',
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,
@@ -29,11 +29,10 @@ const lagos: Filters = [
},
];
const tokyo: Filters = [
{name: 'colorMatrix', type: 'saturate', values: [1.5]},
{name: 'feColorMatrix', type: 'saturate', values: [1.5]},
{
name: 'colorMatrix',
name: 'feColorMatrix',
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,
@@ -41,11 +40,11 @@ const tokyo: Filters = [
},
];
const saturated: Filters = [
{name: 'colorMatrix', type: 'saturate', values: [1.5]},
{name: 'feColorMatrix', type: 'saturate', values: [1.5]},
];
const boring: Filters = [
{
name: 'colorMatrix',
name: 'feColorMatrix',
type: 'matrix',
values: [
0.6965, 0.3845, 0.0945, 0, 0, 0.1745, 0.8430000000000001, 0.084, 0, 0,
@@ -131,10 +130,10 @@ const styles = StyleSheet.create({
const icon = (
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={img}
width={30}
height={30}
style={{filter: 'saturate(2.5)'}}
/>
);
@@ -4,26 +4,53 @@ import {FilterImage} from 'react-native-svg/filter-image';
const testImage = require('../../assets/image.jpg');
const FilterImageLocalExample = () => {
const FilterImageLocalExampleStyleCSS = () => {
return (
<View>
<FilterImage source={testImage} style={{filter: 'saturate(0.5)'}} />
</View>
);
};
FilterImageLocalExampleStyleCSS.title = 'With style filter CSS';
const FilterImageLocalExampleStyleSVG = () => {
return (
<View>
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={testImage}
style={{
filter: [{name: 'feColorMatrix', type: 'saturate', values: 0.5}],
}}
/>
</View>
);
};
FilterImageLocalExample.title = 'Local image with filter';
FilterImageLocalExampleStyleSVG.title = 'With style filter SVG';
const FilterImageLocalExamplePropSVG = () => {
return (
<View>
<FilterImage
source={testImage}
filters={[{name: 'feColorMatrix', type: 'saturate', values: 0.5}]}
/>
</View>
);
};
FilterImageLocalExamplePropSVG.title = 'With prop filters SVG';
const icon = (
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={testImage}
width={30}
height={30}
style={{filter: 'saturate(0.5)'}}
/>
);
const samples = [FilterImageLocalExample];
const samples = [
FilterImageLocalExampleStyleCSS,
FilterImageLocalExampleStyleSVG,
FilterImageLocalExamplePropSVG,
];
export {icon, samples};
@@ -1,32 +1,44 @@
import React from 'react';
import {View} from 'react-native';
import {StyleSheet, 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 = () => {
const FilterImageRemoteExampleCSS = () => {
return (
<View>
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [3]}]}
source={testSource}
style={{width: 200, height: 200}}
style={[styles.image, {filter: 'saturate(3)'}]}
/>
</View>
);
};
FilterImageRemoteExample.title = 'Remote image with filter';
FilterImageRemoteExampleCSS.title = 'Remote image with CSS filter';
const FilterImageRemoteExample = () => {
return (
<View>
<FilterImage
source={testSource}
style={styles.image}
filters={[{name: 'feColorMatrix', type: 'saturate', values: [3]}]}
/>
</View>
);
};
FilterImageRemoteExample.title = 'Remote image with prop filters';
const FilterImageFewFiltersExample = () => {
return (
<View>
<FilterImage
filters={[
{name: 'colorMatrix', type: 'saturate', values: [10]},
{name: 'feColorMatrix', type: 'saturate', values: [10]},
{
name: 'colorMatrix',
name: 'feColorMatrix',
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',
},
@@ -41,12 +53,23 @@ FilterImageFewFiltersExample.title = 'Remote image with filters';
const icon = (
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={testSource}
width={30}
height={30}
style={{filter: 'saturate(0.5)'}}
/>
);
const samples = [FilterImageRemoteExample, FilterImageFewFiltersExample];
const styles = StyleSheet.create({
image: {
width: 200,
height: 200,
},
});
const samples = [
FilterImageRemoteExampleCSS,
FilterImageRemoteExample,
FilterImageFewFiltersExample,
];
export {icon, samples};
@@ -53,10 +53,10 @@ const styles = StyleSheet.create({
const icon = (
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={require('../../assets/image.jpg')}
width={30}
height={30}
style={{filter: 'saturate(3.5)'}}
/>
);