Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop Shadows #217

Merged
merged 14 commits into from
Mar 4, 2022
4 changes: 2 additions & 2 deletions docs/docs/backdrop-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Creates a backdrop blur. All properties from the [group component](/docs/group)

| Name | Type | Description |
|:----------|:--------------------|:---------------------------------------------------------|
| intensity | `number` | intensity of the blur |
| blur | `number` | Blur radius |

## Example

Expand All @@ -76,7 +76,7 @@ const Filter = () => {
fit="cover"
/>
<BackdropBlur
intensity={4}
blur={4}
clip={{ x: 0, y: 128, width: 256, height: 128 }}
>
<Fill color="rgba(0, 0, 0, 0.2)" />
Expand Down
Binary file added docs/docs/image-filters/assets/drop-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions docs/docs/image-filters/drop-shadows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
id: drop-shadows
title: Drop Shadows
sidebar_label: Drop Shadows
slug: /image-filters/drop-shadows
---

The `DropShadow` image filter is equivalent to its [SVG counterpart](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/drop-shadow()).
It creates a filter that draws a drop shadow under the input content.
There is a `shadowOnly` property that renders the drop shadow excluding the input content.


| Name | Type | Description |
|:------------|:--------------|:--------------------------------------------------------------|
| dx | `number` | The X offset of the shadow. |
| dy | `number` | The Y offset of the shadow. |
| blur | `number` | The blur radius for the shadow |
| color | `Color` | The color of the drop shadow |
| cropRect | `IRect` | Optional rectangle that crops the input and output |
| shadowOnly? | `boolean` | If true, the result does not include the input content |
| children? | `ImageFilter` | Optional image filter to be applied first |

## Example

The example below creates two drop shadows.
It is equivalent to the following CSS notation

```css
.paint {
filter: drop-shadow(12px 12px 25px #93b8c4) drop-shadow(-12px -12px 25px #c7f8ff);
}
```

```tsx twoslash
import {
DropShadow,
Fill,
Group,
Paint,
RoundedRect,
Canvas
} from "@shopify/react-native-skia";

const Neumorphism = () => {
return (
<Canvas style={{ width: 256, height: 256 }}>
<Fill color="lightblue" />
<Group>
<Paint>
<DropShadow dx={12} dy={12} blur={25} color="#93b8c4" />
<DropShadow dx={-12} dy={-12} blur={25} color="#c7f8ff" />
</Paint>
<RoundedRect x={32} y={32} width={192} height={192} rx={32} color="lightblue" />
</Group>
</Canvas>
);
};
```

### Result

![Drop Shadow](assets/drop-shadow.png)
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const sidebars = {
items: [
"mask-filters",
"color-filters",
"image-filters/drop-shadows",
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
"image-filters",
"path-effects",
"backdrops-filters",
Expand Down
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@react-navigation/native": "^6.0.6",
"@react-navigation/native-stack": "^6.2.5",
"@shopify/react-native-skia": "link:../package/",
"color": "^4.2.1",
"react": "17.0.2",
"react-native": "0.66.2",
"react-native-safe-area-context": "^3.3.2",
Expand Down
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Matrix } from "./Examples/Matrix";
import { Aurora } from "./Examples/Aurora";
import { Glassmorphism } from "./Examples/Glassmorphism";
import { HomeScreen } from "./Home";
import { Neumorphism } from "./Examples/Neumorphism/Neumorphism";
wcandillon marked this conversation as resolved.
Show resolved Hide resolved

const App = () => {
const Stack = createNativeStackNavigator();
Expand Down Expand Up @@ -55,6 +56,7 @@ const App = () => {
header: () => null,
}}
/>
<Stack.Screen name="Neumorphism" component={Neumorphism} />
<Stack.Screen name="Drawing" component={DrawingExample} />
<Stack.Screen name="Graphs" component={GraphsScreen} />
<Stack.Screen name="Animation" component={AnimationExample} />
Expand Down
2 changes: 1 addition & 1 deletion example/src/Examples/Glassmorphism/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const Glassmorphism = () => {
<Ball r={75} c={vec(300, height / 2 - 200)} />
<BackdropBlur
clip={clip}
intensity={15}
blur={15}
color="rgba(255, 255, 255, 0.1)"
transform={() => [{ translateY: y.value }, { translateX: x.value }]}
>
Expand Down
36 changes: 36 additions & 0 deletions example/src/Examples/Neumorphism/Neumorphism.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
DropShadow,
Fill,
Group,
Paint,
RoundedRect,
} from "@shopify/react-native-skia";
import React from "react";

import { ExportableCanvas } from "../../components/ExportableCanvas";

const PADDING = 32;
const SIZE = 256 - 64;
const R = 32;

export const Neumorphism = () => {
return (
<ExportableCanvas style={{ width: 256, height: 256 }}>
<Fill color="lightblue" />
<Group>
<Paint>
<DropShadow dx={12} dy={12} blur={25} color="#93b8c4" />
<DropShadow dx={-12} dy={-12} blur={25} color="#c7f8ff" />
</Paint>
<RoundedRect
x={PADDING}
y={PADDING}
width={SIZE}
height={SIZE}
rx={R}
color="lightblue"
/>
</Group>
</ExportableCanvas>
);
};
1 change: 1 addition & 0 deletions example/src/Examples/Neumorphism/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Neumorphism } from "./Neumorphism";
2 changes: 2 additions & 0 deletions example/src/Examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export * from "./Filters";
export * from "./Gooey";
export * from "./Matrix";
export * from "./Graphs";
export * from "./Aurora";
export * from "./Neumorphism";
17 changes: 8 additions & 9 deletions example/src/Home/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react";
import { StyleSheet, View } from "react-native";
import { ScrollView } from "react-native";

import { HomeScreenButton } from "./HomeScreenButton";

export const HomeScreen = () => {
return (
<View style={styles.container}>
<ScrollView>
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
<HomeScreenButton title="API" description="API examples" route="API" />
<HomeScreenButton
title="🧘 Breathe"
Expand Down Expand Up @@ -52,17 +52,16 @@ export const HomeScreen = () => {
description="Animated graphs with Skia"
route="Graphs"
/>
<HomeScreenButton
title="💚 Neumorphism"
description="Drop Shadows"
route="Neumorphism"
/>
<HomeScreenButton
title="🎥 Animation"
description="Animated with Skia"
route="Animation"
/>
</View>
</ScrollView>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
},
});
34 changes: 34 additions & 0 deletions example/src/components/ExportableCanvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { CanvasProps, SkiaView } from "@shopify/react-native-skia";
import { ImageFormat, Canvas } from "@shopify/react-native-skia";
import React, { useCallback, useRef } from "react";
import { Alert, Share, TouchableWithoutFeedback, View } from "react-native";

export const ExportableCanvas = ({ children, style }: CanvasProps) => {
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
const ref = useRef<SkiaView>(null);
const handleShare = useCallback(() => {
const image = ref.current?.makeImageSnapshot();
if (image) {
const data = image.encodeToBase64(ImageFormat.PNG, 100);
const url = `data:image/png;base64,${data}`;
Share.share({
url,
title: "Drawing",
}).catch(() => {
Alert.alert("An error occurred when sharing the image.");
});
} else {
Alert.alert(
"An error occurred when creating a snapshot of your drawing."
);
}
}, [ref]);
return (
<TouchableWithoutFeedback onPress={handleShare}>
<View style={{ flex: 1 }}>
<Canvas style={style} ref={ref}>
{children}
</Canvas>
</View>
</TouchableWithoutFeedback>
);
};
35 changes: 31 additions & 4 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1239,9 +1239,8 @@
nanoid "^3.1.23"

"@shopify/react-native-skia@link:../package":
version "0.1.37"
dependencies:
react-reconciler "^0.26.2"
version "0.0.0"
uid ""

"@sideway/address@^4.1.0":
version "4.1.2"
Expand Down Expand Up @@ -2218,11 +2217,27 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=

color-name@~1.1.4:
color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==

color-string@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa"
integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"

color@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884"
integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw==
dependencies:
color-convert "^2.0.1"
color-string "^1.9.0"

colorette@^1.0.7:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
Expand Down Expand Up @@ -3571,6 +3586,11 @@ is-arrayish@^0.2.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=

is-arrayish@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==

is-bigint@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
Expand Down Expand Up @@ -6189,6 +6209,13 @@ simple-plist@^1.0.0:
bplist-parser "0.3.0"
plist "^3.0.4"

simple-swizzle@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=
dependencies:
is-arrayish "^0.3.1"

sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
Expand Down
27 changes: 23 additions & 4 deletions package/cpp/api/JsiSkImageFilterFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,12 @@ class JsiSkImageFilterFactory : public JsiSkHostObject {
}

JSI_HOST_FUNCTION(MakeCompose) {
auto outer = JsiSkImageFilter::fromValue(runtime, arguments[0]);
sk_sp<SkImageFilter> outer;
if (!arguments[0].isNull()) {
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
outer = JsiSkImageFilter::fromValue(runtime, arguments[0]);
}
sk_sp<SkImageFilter> inner;
if (arguments[1].isNull()) {
if (!arguments[1].isNull()) {
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
inner = JsiSkImageFilter::fromValue(runtime, arguments[1]);
}
return jsi::Object::createFromHostObject(
Expand All @@ -97,11 +100,19 @@ class JsiSkImageFilterFactory : public JsiSkHostObject {
auto sigmaX = arguments[2].asNumber();
auto sigmaY = arguments[3].asNumber();
auto color = arguments[4].asNumber();
sk_sp<SkImageFilter> input;
if (!arguments[5].isNull()) {
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
input = JsiSkImageFilter::fromValue(runtime, arguments[5]);
}
SkImageFilters::CropRect cropRect = {};
if (count > 6 && !arguments[6].isUndefined()) {
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
cropRect = *JsiSkRect::fromValue(runtime, arguments[6]);
}
return jsi::Object::createFromHostObject(
runtime,
std::make_shared<JsiSkImageFilter>(
getContext(), SkImageFilters::DropShadow(dx, dy, sigmaX, sigmaY,
color, nullptr)));
color, input, cropRect)));
}

JSI_HOST_FUNCTION(MakeDropShadowOnly) {
Expand All @@ -110,11 +121,19 @@ class JsiSkImageFilterFactory : public JsiSkHostObject {
auto sigmaX = arguments[2].asNumber();
auto sigmaY = arguments[3].asNumber();
auto color = arguments[4].asNumber();
sk_sp<SkImageFilter> input;
if (!arguments[5].isNull()) {
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
input = JsiSkImageFilter::fromValue(runtime, arguments[5]);
}
SkImageFilters::CropRect cropRect = {};
if (count > 6 && !arguments[6].isUndefined()) {
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
cropRect = *JsiSkRect::fromValue(runtime, arguments[6]);
}
return jsi::Object::createFromHostObject(
runtime,
std::make_shared<JsiSkImageFilter>(
getContext(), SkImageFilters::DropShadowOnly(dx, dy, sigmaX, sigmaY,
color, nullptr)));
color, input, cropRect)));
}

JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeBlur),
Expand Down
11 changes: 9 additions & 2 deletions package/src/renderer/components/Paint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { isShader } from "../../skia/Shader/Shader";
import { isMaskFilter } from "../../skia/MaskFilter";
import { isColorFilter } from "../../skia/ColorFilter/ColorFilter";
import { Skia } from "../../skia/Skia";
import type { IImageFilter } from "../../skia/ImageFilter/ImageFilter";
wcandillon marked this conversation as resolved.
Show resolved Hide resolved
import { isImageFilter } from "../../skia/ImageFilter/ImageFilter";
import type { CustomPaintProps } from "../processors";
import { processPaint } from "../processors";
Expand All @@ -32,12 +33,18 @@ export const Paint = forwardRef<IPaint, AnimatedProps<PaintProps>>(
paint.setMaskFilter(child);
} else if (isColorFilter(child)) {
paint.setColorFilter(child);
} else if (isImageFilter(child)) {
paint.setImageFilter(child);
} else if (isPathEffect(child)) {
paint.setPathEffect(child);
}
});
paint.setImageFilter(
children
.filter(isImageFilter)
.reduce<IImageFilter | null>(
(acc, filter) => Skia.ImageFilter.MakeCompose(acc, filter),
null
)
);
return paint;
});
return <skDeclaration declaration={declaration} {...props} />;
Expand Down
Loading