エフェクトを実装する
FilterEffect はレンダーターゲットを後処理する EngineObject です。基本的な書き方は描画オブジェクトを実装と同じで、partial class で宣言し、IExpressionProperty<T> メンバを公開し、ApplyTo の中で自動生成された Resource からスナップショット値を読み取ります。
EngineObject リファレンス を読んでいない場合は先に目を通してください。プロパティシステムや Resource のライフサイクルがそこで説明されています。
1. 最小のエフェクト
using Beutl.Graphics.Effects;
using Beutl.Graphics.Rendering;
namespace MyExtension;
public sealed partial class MyFilterEffect : FilterEffect
{
public MyFilterEffect()
{
ScanProperties<MyFilterEffect>();
}
public override void ApplyTo(FilterEffectContext context, FilterEffect.Resource resource)
{
// Add filter operations to context here.
}
}
Drawable との違いは 2 つ:
ApplyToはFilterEffectContextと自動生成されたResourceを受け取ります。プロパティの値を読むにはresourceを派生Resource型にキャストします。MeasureCore/OnDrawはありません。エディタがFilterEffectContextを介してレンダーターゲットを流し込んでくれるので、それをどう変換するかだけを記述します。
2. 既存のエフェクトを組み合わせる
最も簡単なカスタムエフェクトは、FilterEffectContext のメソッドを使って Beutl 組み込みの操作を重ねていく方法です。境界の自動拡張(例:ブラーは表面を広げる)も内部で処理されるため、サイズ調整を自分で書く必要はほぼありません。
using System.ComponentModel.DataAnnotations;
using Beutl;
using Beutl.Engine;
using Beutl.Graphics;
using Beutl.Graphics.Effects;
using Beutl.Graphics.Rendering;
using Beutl.Media;
namespace MyExtension;
public sealed partial class HighContrastBlur : FilterEffect
{
public HighContrastBlur()
{
ScanProperties<HighContrastBlur>();
}
[Display(Name = "Blur Sigma")]
[Range(0, float.MaxValue)]
public IExpressionProperty<float> BlurSigma { get; } = Property.CreateAnimatable<float>(20);
[Display(Name = "Contrast")]
[Range(-100, 100)]
public IExpressionProperty<float> Contrast { get; } = Property.CreateAnimatable<float>(20);
public override void ApplyTo(FilterEffectContext context, FilterEffect.Resource resource)
{
var r = (Resource)resource;
context.Blur(new Size(r.BlurSigma, r.BlurSigma));
context.HighContrast(
grayscale: false,
invertStyle: HighContrastInvertStyle.NoInvert,
contrast: r.Contrast / 100f);
}
}
FilterEffectContext には組み込み操作それぞれに対応するメソッドが用意されています:Blur、DropShadow、InnerShadow、Erode、Dilate、ColorMatrix、Saturate、HueRotate、Brightness、HighContrast、Lighting、MatrixConvolution、Transform、BlendMode など。完全なリストは FilterEffectContext.cs と FilterEffectContext.Operations.cs を参照してください。
Crop(Rect, GradientSpreadMethod = Decal) は Bounds と同じ座標系の矩形を切り出します。既定の Decal は範囲外を透明にして境界を縮めます。Pad は端の色を延長し、Repeat は繰り返し、Reflect は反転して繰り返し、元の境界を保ちます。
複数入力や複数パスのGLSL処理については、C#スクリプトも参照してください。
3. レンダーターゲットを直接処理する
FlatShadow.cs や ColorShift.cs、GLSLScriptEffect.cs などを参考にしてください。
CustomEffect(data, action, transformBounds) は明示的な状態を受け取り、その状態から出力境界を計算します。記録時に出力境界が決まらない場合、transformBounds を渡さないオーバーロードは、所有するターゲットの有限の領域が決まるまで境界を未確定として扱います。メタデータのコールバックから、編集中の変更可能なオブジェクトを直接参照しないでください。
FilterEffectContext.Shader(ShaderDescription) と Geometry(GeometryDescription) は、境界とヒットテストの契約を持つ遅延操作を記録します。操作の選択判定は RenderHitTestContract.None、OutputBounds、AnyInput、Custom(...) で宣言します。default(RenderHitTestContract) は無効です。独自のコールバックには Custom(state, static (state, context, point) => ...) で状態を渡せます。
AppendSkiaFilter の factory はフィルターの生成だけを行い、Activate、Flush などで自身の FilterEffectExecutor に再入してはいけません。ターゲットへの描画は CustomEffect の action に移します。
メタデータコールバックの診断
コンパイラーが BESG004 を報告した場合は、メタデータコールバックと、その中から呼ぶ処理が参照している値を確認してください。変更される静的な値を直接読み取らず、明示的な状態や束縛された描画リソースを通じて渡します。
4. 拡張機能で登録する
using Beutl.Extensibility;
using Beutl.Services;
namespace MyExtension;
[Export]
public sealed class MyEffectExtension : Extension
{
public override string Name => "My Effects";
public override string DisplayName => "My Effects";
public override void Load()
{
LibraryService.Current.Register<HighContrastBlur>(
KnownLibraryItemFormats.FilterEffect,
"High-Contrast Blur");
}
}
複数のエフェクトをまとめたい場合は、EngineObject リファレンス で説明している RegisterGroup ヘルパーを使うと、ライブラリ上で 1 つのカテゴリにグルーピングできます。
Beutl ソースの参考ファイル
| ファイル | 学べること |
|---|---|
src/Beutl.Engine/Graphics/FilterEffects/HighContrast.cs | FilterEffectContext の単一呼び出しで完結する最小エフェクト。 |
src/Beutl.Engine/Graphics/FilterEffects/FlatShadow.cs | CustomEffect で独自描画を行い、transformBounds で境界を拡張する例。 |
src/Beutl.Engine/Graphics/FilterEffects/FilterEffectContext.cs | チェイン可能な組み込み操作の全リスト。 |