Skip to content
React Native Skia ガイドブック
Esc
navigateopen⌘Jpreview
On this page

高度なイメージフィルター

DisplacementMap・Offset・Morphology・RuntimeShaderをイメージフィルターとして使う方法、背景を覗き込むBackdropFilter/BackdropBlurを解説します。

前章のBlur・Shadowに続いて、この章ではより特殊な変形を行うイメージフィルターと、背景そのものにフィルターをかけるバックドロップフィルター(backdrop filter)を扱います。いずれもSVGフィルターにおなじみの効果に対応するものが多く、Web開発の経験があれば理解しやすいはずです。

DisplacementMap: ピクセルをずらす

DisplacementMapは、SVGのfeDisplacementMapと同じ考え方のフィルターです。子要素として渡した別の画像(変位マップ)の色チャンネルの値を使って、元の画像のピクセルを空間的にずらします。

Name Type Description
channelX ColorChannel X軸方向のずらし量に使うチャンネル(rgba)
channelY ColorChannel Y軸方向のずらし量に使うチャンネル(rgba)
scale number ずらし量の倍率
children? ImageFilter 先に適用する別のイメージフィルター

以下の例は、シェーダーの実践で扱うパーリンノイズ(Perlin noise)のTurbulenceシェーダーを変位マップとして使い、写真をガラス越しに見たような形に歪ませています。

import { Canvas, Image, Turbulence, DisplacementMap, useImage } from "@shopify/react-native-skia";

const Filter = () => {
  const image = useImage(require("./assets/photo.jpg"));
  if (!image) {
    return null;
  }
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Image image={image} x={0} y={0} width={256} height={256} fit="cover">
        <DisplacementMap channelX="g" channelY="a" scale={20}>
          <Turbulence freqX={0.01} freqY={0.05} octaves={2} seed={2} />
        </DisplacementMap>
      </Image>
    </Canvas>
  );
};

Skiaを読み込み中…

scaleを大きくするほどノイズの起伏に沿ってピクセルが大きくずれます。0にすると変位マップの影響がなくなり、元の画像に戻ります。

Offset: 単純にずらす

OffsetはSVGのfeOffset相当で、コンテンツ全体をX・Y方向に平行移動するだけのシンプルなフィルターです。Grouptransformpropでも同じ移動はできますが、Offsetはイメージフィルターのチェーンの中でずらし処理を挟みたいときに使います。

import { Canvas, Image, Offset, useImage, Fill } from "@shopify/react-native-skia";

const Filter = () => {
  const image = useImage(require("./assets/photo.jpg"));
  if (!image) {
    return null;
  }
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Fill color="lightblue" />
      <Image image={image} x={0} y={0} width={256} height={256} fit="cover">
        <Offset x={64} y={64} />
      </Image>
    </Canvas>
  );
};

Skiaを読み込み中…

Morphology: 太らせる・細らせる

MorphologyはSVGのfeMorphology相当で、operator="dilate"(膨張、太らせる)かoperator="erode"(収縮、細らせる)を選んで、指定した半径ぶん形状を変化させます。テキストを太字っぽく見せたり、逆に細く削ったりする用途で使われます。

Name Type Description
operator erode | dilate 収縮か膨張か(デフォルトはdilate)
radius number | Vector 効果の半径
children? ImageFilter 先に適用する別のイメージフィルター

Skiaを読み込み中…

dilateはフォントを太字にしたようなインパクトのある見た目になり、erodeは細く鋭い印象になります。radiusを大きくしすぎると文字の形が崩れて判読しにくくなるため、実用ではごく小さな値(0.5〜2程度)に留めるのが無難です。

RuntimeShaderをイメージフィルターとして使う

自作のSkSLシェーダーをイメージフィルターとして適用するRuntimeShaderもあります。詳しい書き方はシェーダー入門とSkSLの章でまとめて扱いますが、既存のコンテンツに対して自由な画素処理をかけられる強力なフィルターだという点だけ、ここで押さえておいてください。

import { Canvas, RuntimeShader, Skia, Group, Circle } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
uniform shader image;

half4 main(float2 xy) {
  return image.eval(xy).rbga;
}
`)!;

const RuntimeShaderDemo = () => {
  const r = 128;
  return (
    <Canvas style={{ flex: 1 }}>
      <Group>
        <RuntimeShader source={source} />
        <Circle cx={r} cy={r} r={r} color="lightblue" />
      </Group>
    </Canvas>
  );
};

uniform shader imageという変数名で、フィルターがかかる直前の描画結果をshaderとして受け取れます。image.eval(xy)でそのピクセル色を取得し、チャンネルを入れ替えたり計算したりして返すのが基本の書き方です。以下はRGBの3円を重ねた上で、スウィズルの並びを切り替えて色がどう入れ替わるか確認できるデモです。

ただしこの<RuntimeShader source={source} />をイメージフィルターとして使う書き方は、Web版のCanvasKitでは動作しません。内部で呼ばれるImageFilter.MakeRuntimeShaderがWeb版では未実装で、呼び出すと例外を投げます(react-native-skiaの現行バージョンでの制約で、本書のデモ環境を含むWeb全般に共通します)。

このあとの実演デモでは、この制約を避けるため上のコードとは異なる実装を使っています。RGBの3円を描いた別Canvasをスナップショットして画像化し、ShaderImageShaderを組み合わせてuniform shader imageとして渡す、という回避策です。見た目の結果は上のコードと同じですが、実装方法が異なる点に注意してください。

ネイティブ(iOS/Android)ではこの制約がなく、上のコードのままで動作します。

Skiaを読み込み中…

BackdropFilter / BackdropBlur: 背景を覗き込む

ここまでのフィルターは「これから描く内容」にかかるものでしたが、BackdropFilterは逆に「すでに描かれている背景」にフィルターをかけます。CSSのbackdrop-filterと同じ考え方で、Groupを拡張したコンポーネントなのでクリッピング用のclippropを併用します。

import {
  Canvas,
  BackdropFilter,
  Image,
  ColorMatrix,
  useImage,
} from "@shopify/react-native-skia";

const BLACK_AND_WHITE = [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0];

export const Filter = () => {
  const image = useImage(require("./assets/photo.jpg"));
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Image image={image} x={0} y={0} width={256} height={256} fit="cover" />
      <BackdropFilter
        clip={{ x: 0, y: 128, width: 256, height: 128 }}
        filter={<ColorMatrix matrix={BLACK_AND_WHITE} />}
      />
    </Canvas>
  );
};

よく使われるのがぼかしを組み合わせたBackdropBlurで、いわゆる「すりガラス」表現に使えます。

Name Type Description
blur number ぼかし半径

Skiaを読み込み中…

BackdropBlurの子要素に半透明のFillを重ねると、モーダルやボトムシートの背後によくあるガラス風の質感になります。clipで対象範囲を絞り込むのを忘れると、キャンバス全体がぼやけてしまうので注意してください。

注意点

DisplacementMapRuntimeShaderのように画素ごとの複雑な計算を行うフィルターは、対象領域が大きいほど処理コストが上がります。特にアニメーションで毎フレーム再計算する場合は、対象を小さくする・scaleを抑える・実機で確認するといった対策を検討してください。

公式ドキュメントのDisplacement MapMorphologyRuntime ShaderBackdrop Filtersも参照してください。