@shopify/react-native-skia
Version:
High-performance React Native Graphics using Skia
305 lines (288 loc) • 7.63 kB
text/typescript
import {
enumKey,
fitRects,
getRect,
processColor,
processGradientProps,
processTransformProps,
rect2rect,
} from "../../../dom/nodes";
import { NodeType } from "../../../dom/types";
import type {
BlendProps,
ColorProps,
FractalNoiseProps,
ImageShaderProps,
LinearGradientProps,
RadialGradientProps,
ShaderProps,
SweepGradientProps,
TurbulenceProps,
TwoPointConicalGradientProps,
} from "../../../dom/types";
import type { SkShader } from "../../../skia/types";
import {
BlendMode,
FilterMode,
isCubicSampling,
MipmapMode,
processUniforms,
TileMode,
} from "../../../skia/types";
import { composeDeclarations } from "../../utils";
import type { Command } from "../Core";
import { CommandType } from "../Core";
import type { DrawingContext } from "../DrawingContext";
const declareShader = (
ctx: DrawingContext,
props: ShaderProps,
children: number
) => {
"worklet";
const { source, uniforms, ...transform } = props;
const m3 = ctx.Skia.Matrix();
processTransformProps(m3, transform);
const shader = source.makeShaderWithChildren(
processUniforms(source, uniforms),
ctx.shaders.splice(0, children),
m3
);
ctx.shaders.push(shader);
};
const declareColorShader = (ctx: DrawingContext, props: ColorProps) => {
"worklet";
const { color } = props;
const shader = ctx.Skia.Shader.MakeColor(processColor(ctx.Skia, color));
ctx.shaders.push(shader);
};
const declareFractalNoiseShader = (
ctx: DrawingContext,
props: FractalNoiseProps
) => {
"worklet";
const { freqX, freqY, octaves, seed, tileWidth, tileHeight } = props;
const shader = ctx.Skia.Shader.MakeFractalNoise(
freqX,
freqY,
octaves,
seed,
tileWidth,
tileHeight
);
ctx.shaders.push(shader);
};
const declareTwoPointConicalGradientShader = (
ctx: DrawingContext,
props: TwoPointConicalGradientProps
) => {
"worklet";
const { startR, endR, start, end } = props;
const { colors, positions, mode, localMatrix, flags } = processGradientProps(
ctx.Skia,
props
);
const shader = ctx.Skia.Shader.MakeTwoPointConicalGradient(
start,
startR,
end,
endR,
colors,
positions,
mode,
localMatrix,
flags
);
ctx.shaders.push(shader);
};
const declareRadialGradientShader = (
ctx: DrawingContext,
props: RadialGradientProps
) => {
"worklet";
const { c, r } = props;
const { colors, positions, mode, localMatrix, flags } = processGradientProps(
ctx.Skia,
props
);
const shader = ctx.Skia.Shader.MakeRadialGradient(
c,
r,
colors,
positions,
mode,
localMatrix,
flags
);
ctx.shaders.push(shader);
};
const declareSweepGradientShader = (
ctx: DrawingContext,
props: SweepGradientProps
) => {
"worklet";
const { c, start, end } = props;
const { colors, positions, mode, localMatrix, flags } = processGradientProps(
ctx.Skia,
props
);
const shader = ctx.Skia.Shader.MakeSweepGradient(
c.x,
c.y,
colors,
positions,
mode,
localMatrix,
flags,
start,
end
);
ctx.shaders.push(shader);
};
const declareLinearGradientShader = (
ctx: DrawingContext,
props: LinearGradientProps
) => {
"worklet";
const { start, end } = props;
const { colors, positions, mode, localMatrix, flags } = processGradientProps(
ctx.Skia,
props
);
const shader = ctx.Skia.Shader.MakeLinearGradient(
start,
end,
colors,
positions ?? null,
mode,
localMatrix,
flags
);
ctx.shaders.push(shader);
};
const declareTurbulenceShader = (
ctx: DrawingContext,
props: TurbulenceProps
) => {
"worklet";
const { freqX, freqY, octaves, seed, tileWidth, tileHeight } = props;
const shader = ctx.Skia.Shader.MakeTurbulence(
freqX,
freqY,
octaves,
seed,
tileWidth,
tileHeight
);
ctx.shaders.push(shader);
};
const declareImageShader = (ctx: DrawingContext, props: ImageShaderProps) => {
"worklet";
const { fit, image, tx, ty, sampling, ...imageShaderProps } = props;
if (!image) {
return;
}
const rct = getRect(ctx.Skia, imageShaderProps);
const m3 = ctx.Skia.Matrix();
if (rct) {
const rects = fitRects(
fit,
{ x: 0, y: 0, width: image.width(), height: image.height() },
rct
);
const [x, y, sx, sy] = rect2rect(rects.src, rects.dst);
m3.translate(x.translateX, y.translateY);
m3.scale(sx.scaleX, sy.scaleY);
}
const lm = ctx.Skia.Matrix();
lm.concat(m3);
processTransformProps(lm, imageShaderProps);
let shader: SkShader;
if (sampling && isCubicSampling(sampling)) {
shader = image.makeShaderCubic(
TileMode[enumKey(tx)],
TileMode[enumKey(ty)],
sampling.B,
sampling.C,
lm
);
} else {
shader = image.makeShaderCubic(
TileMode[enumKey(tx)],
TileMode[enumKey(ty)],
sampling?.filter ?? FilterMode.Linear,
sampling?.mipmap ?? MipmapMode.None,
lm
);
}
ctx.shaders.push(shader);
};
const declareBlend = (ctx: DrawingContext, props: BlendProps) => {
"worklet";
const blend = BlendMode[enumKey(props.mode as BlendProps["mode"])];
const shaders = ctx.shaders.splice(0, ctx.shaders.length);
if (shaders.length > 0) {
const composer = ctx.Skia.Shader.MakeBlend.bind(ctx.Skia.Shader, blend);
ctx.shaders.push(composeDeclarations(shaders, composer));
}
};
export const isPushShader = (
command: Command
): command is Command<CommandType.PushShader> => {
"worklet";
return command.type === CommandType.PushShader;
};
type Props = {
[NodeType.Shader]: ShaderProps;
[NodeType.ImageShader]: ImageShaderProps;
[NodeType.ColorShader]: ColorProps;
[NodeType.Turbulence]: TurbulenceProps;
[NodeType.FractalNoise]: FractalNoiseProps;
[NodeType.LinearGradient]: LinearGradientProps;
[NodeType.RadialGradient]: RadialGradientProps;
[NodeType.SweepGradient]: SweepGradientProps;
[NodeType.TwoPointConicalGradient]: TwoPointConicalGradientProps;
[NodeType.Blend]: BlendProps;
};
interface PushShader<
T extends keyof Props,
> extends Command<CommandType.PushShader> {
shaderType: T;
props: Props[T];
children: number;
}
const isShader = <T extends keyof Props>(
command: Command<CommandType.PushShader>,
type: T
): command is PushShader<T> => {
"worklet";
return command.shaderType === type;
};
export const pushShader = (
ctx: DrawingContext,
command: Command<CommandType.PushShader>
) => {
"worklet";
if (isShader(command, NodeType.Shader)) {
declareShader(ctx, command.props, command.children);
} else if (isShader(command, NodeType.ImageShader)) {
declareImageShader(ctx, command.props);
} else if (isShader(command, NodeType.ColorShader)) {
declareColorShader(ctx, command.props);
} else if (isShader(command, NodeType.Turbulence)) {
declareTurbulenceShader(ctx, command.props);
} else if (isShader(command, NodeType.FractalNoise)) {
declareFractalNoiseShader(ctx, command.props);
} else if (isShader(command, NodeType.LinearGradient)) {
declareLinearGradientShader(ctx, command.props);
} else if (isShader(command, NodeType.RadialGradient)) {
declareRadialGradientShader(ctx, command.props);
} else if (isShader(command, NodeType.SweepGradient)) {
declareSweepGradientShader(ctx, command.props);
} else if (isShader(command, NodeType.TwoPointConicalGradient)) {
declareTwoPointConicalGradientShader(ctx, command.props);
} else if (isShader(command, NodeType.Blend)) {
declareBlend(ctx, command.props);
} else {
throw new Error(`Unknown shader type: ${command.shaderType}`);
}
};