Skip to content

Commit

Permalink
Merge pull request #1070 from b-editor/effects/color-shift
Browse files Browse the repository at this point in the history
Color Shift Effect
  • Loading branch information
yuto-trd authored Aug 24, 2024
2 parents f6ec67c + 4551d53 commit 6caf498
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/Beutl.Engine/Graphics/DrawableGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public override void Render(ICanvas canvas)
using (canvas.PushTransform(transform))
using (FilterEffect == null ? new() : canvas.PushFilterEffect(FilterEffect))
using (OpacityMask == null ? new() : canvas.PushOpacityMask(OpacityMask, new Rect(rect.Size)))
using (canvas.PushLayer())
{
OnDraw(canvas);
}
Expand Down
149 changes: 149 additions & 0 deletions src/Beutl.Engine/Graphics/FilterEffects/ColorShift.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using Beutl.Graphics.Rendering;
using Beutl.Media;
using ILGPU;
using ILGPU.Runtime;
using OpenCvSharp;
using SkiaSharp;

namespace Beutl.Graphics.Effects;

public class ColorShift : FilterEffect
{
public static readonly CoreProperty<PixelPoint> RedOffsetProperty;
public static readonly CoreProperty<PixelPoint> GreenOffsetProperty;
public static readonly CoreProperty<PixelPoint> BlueOffsetProperty;
public static readonly CoreProperty<PixelPoint> AlphaOffsetProperty;
private PixelPoint _redOffset;
private PixelPoint _greenOffset;
private PixelPoint _blueOffset;
private PixelPoint _alphaOffset;

static ColorShift()
{
RedOffsetProperty = ConfigureProperty<PixelPoint, ColorShift>(nameof(RedOffset))
.Accessor(o => o.RedOffset, (o, v) => o.RedOffset = v)
.Register();

GreenOffsetProperty = ConfigureProperty<PixelPoint, ColorShift>(nameof(GreenOffset))
.Accessor(o => o.GreenOffset, (o, v) => o.GreenOffset = v)
.Register();

BlueOffsetProperty = ConfigureProperty<PixelPoint, ColorShift>(nameof(BlueOffset))
.Accessor(o => o.BlueOffset, (o, v) => o.BlueOffset = v)
.Register();

AlphaOffsetProperty = ConfigureProperty<PixelPoint, ColorShift>(nameof(AlphaOffset))
.Accessor(o => o.AlphaOffset, (o, v) => o.AlphaOffset = v)
.Register();

AffectsRender<ColorShift>(
RedOffsetProperty, GreenOffsetProperty, BlueOffsetProperty, AlphaOffsetProperty);
}

public PixelPoint RedOffset
{
get => _redOffset;
set => SetAndRaise(RedOffsetProperty, ref _redOffset, value);
}

public PixelPoint GreenOffset
{
get => _greenOffset;
set => SetAndRaise(GreenOffsetProperty, ref _greenOffset, value);
}

public PixelPoint BlueOffset
{
get => _blueOffset;
set => SetAndRaise(BlueOffsetProperty, ref _blueOffset, value);
}

public PixelPoint AlphaOffset
{
get => _alphaOffset;
set => SetAndRaise(AlphaOffsetProperty, ref _alphaOffset, value);
}

public override void ApplyTo(FilterEffectContext context)
{
context.CustomEffect((RedOffset, GreenOffset, BlueOffset, AlphaOffset), OnApply, TransformBoundsCore);
}

private static Rect TransformBoundsCore(
(PixelPoint RedOffset, PixelPoint GreenOffset, PixelPoint BlueOffset, PixelPoint AlphaOffset) d,
Rect bounds)
{
return bounds.Translate(d.RedOffset.ToPoint(1))
.Union(bounds.Translate(d.GreenOffset.ToPoint(1)))
.Union(bounds.Translate(d.BlueOffset.ToPoint(1)))
.Union(bounds.Translate(d.AlphaOffset.ToPoint(1)));
}

private static void OnApply(
(PixelPoint RedOffset, PixelPoint GreenOffset, PixelPoint BlueOffset, PixelPoint AlphaOffset) data,
CustomFilterEffectContext context)
{
for (int i = 0; i < context.Targets.Count; i++)
{
var target = context.Targets[i];
var surface = target.Surface!;

var bounds = TransformBoundsCore(data, target.Bounds);
var pixelRect = PixelRect.FromRect(bounds);
int minOffsetX = Math.Min(data.RedOffset.X,
Math.Min(data.GreenOffset.X, Math.Min(data.BlueOffset.X, data.AlphaOffset.X)));
int minOffsetY = Math.Min(data.RedOffset.Y,
Math.Min(data.GreenOffset.Y, Math.Min(data.BlueOffset.Y, data.AlphaOffset.Y)));

var size = surface.Value.Canvas.DeviceClipBounds.Size;
Accelerator accelerator = SharedGPUContext.Accelerator;
var kernel = accelerator.LoadAutoGroupedStreamKernel<
Index2D, ArrayView2D<Vec4b, Stride2D.DenseX>, ArrayView2D<Vec4b, Stride2D.DenseX>,
PixelPoint, PixelPoint, PixelPoint, PixelPoint, PixelPoint>(
Kernel);
using var source = accelerator.Allocate2DDenseX<Vec4b>(new(size.Width, size.Height));
using var dest = accelerator.Allocate2DDenseX<Vec4b>(new(pixelRect.Width, pixelRect.Height));

SharedGPUContext.CopyFromCPU(source, surface.Value,
new SKImageInfo(size.Width, size.Height, SKColorType.Bgra8888));

kernel(
new Index2D(size.Width, size.Height),
source, dest,
data.RedOffset, data.GreenOffset, data.BlueOffset, data.AlphaOffset,
new PixelPoint(minOffsetX, minOffsetY));

using var skBmp =
new SKBitmap(new SKImageInfo(pixelRect.Width, pixelRect.Height, SKColorType.Bgra8888));

SharedGPUContext.CopyToCPU(dest, skBmp);

EffectTarget newTarget = context.CreateTarget(bounds);
newTarget.Surface!.Value.Canvas.DrawBitmap(skBmp, 0,0);

target.Dispose();
context.Targets[i] = newTarget;
}
}

private static void Kernel(
Index2D index, ArrayView2D<Vec4b, Stride2D.DenseX> src, ArrayView2D<Vec4b, Stride2D.DenseX> dst,
PixelPoint redOffset, PixelPoint greenOffset, PixelPoint blueOffset, PixelPoint alphaOffset,
PixelPoint minOffset)
{
var color = src[index.X, index.Y];

dst[index.X + redOffset.X - minOffset.X, index.Y + redOffset.Y - minOffset.Y].Item2 = color.Item2;
dst[index.X + greenOffset.X - minOffset.X, index.Y + greenOffset.Y - minOffset.Y].Item1 =
color.Item1;
dst[index.X + blueOffset.X - minOffset.X, index.Y + blueOffset.Y - minOffset.Y].Item0 =
color.Item0;
dst[index.X + alphaOffset.X - minOffset.X, index.Y + alphaOffset.Y - minOffset.Y].Item3 =
color.Item3;
}

public override Rect TransformBounds(Rect bounds)
{
return TransformBoundsCore((RedOffset, GreenOffset, BlueOffset, AlphaOffset), bounds);
}
}
5 changes: 5 additions & 0 deletions src/Beutl.Engine/Graphics/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ public static Mat ToMat(this Bitmap<Bgra8888> self)
return new Mat(self.Height, self.Width, MatType.CV_8UC4, self.Data);
}

public static Mat AsMat(this Bitmap<Bgra8888> self)
{
return new Mat(self.Height, self.Width, MatType.CV_8UC4, self.Data);
}

internal static EncodedImageFormat ToImageFormat(string filename)
{
string? ex = Path.GetExtension(filename);
Expand Down
46 changes: 45 additions & 1 deletion src/Beutl.Engine/Graphics/Rendering/SharedGPUContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using ILGPU;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ILGPU;
using ILGPU.Runtime;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using SkiaSharp;

namespace Beutl.Graphics.Rendering;

Expand Down Expand Up @@ -44,4 +48,44 @@ public static void Shutdown()
s_context = null;
});
}

public static unsafe void CopyFromCPU(MemoryBuffer1D<Vec4b, Stride1D.Dense> source, SKSurface surface, SKImageInfo imageInfo)
{
void* tmp = NativeMemory.Alloc((nuint)source.LengthInBytes);
try
{
bool result = surface.ReadPixels(imageInfo, (nint)tmp, imageInfo.Width * 4, 0, 0);

source.View.CopyFromCPU(ref Unsafe.AsRef<Vec4b>(tmp), source.Length);
}
finally
{
NativeMemory.Free(tmp);
}
}

public static unsafe void CopyToCPU(MemoryBuffer1D<Vec4b, Stride1D.Dense> source, SKBitmap bitmap)
{
source.View.CopyToCPU(ref Unsafe.AsRef<Vec4b>((void*)bitmap.GetPixels()), source.Length);
}

public static unsafe void CopyFromCPU(MemoryBuffer2D<Vec4b, Stride2D.DenseX> source, SKSurface surface, SKImageInfo imageInfo)
{
void* tmp = NativeMemory.Alloc((nuint)source.LengthInBytes);
try
{
bool result = surface.ReadPixels(imageInfo, (nint)tmp, imageInfo.Width * 4, 0, 0);

source.View.BaseView.CopyFromCPU(ref Unsafe.AsRef<Vec4b>(tmp), source.Length);
}
finally
{
NativeMemory.Free(tmp);
}
}

public static unsafe void CopyToCPU(MemoryBuffer2D<Vec4b, Stride2D.DenseX> source, SKBitmap bitmap)
{
source.View.BaseView.CopyToCPU(ref Unsafe.AsRef<Vec4b>((void*)bitmap.GetPixels()), source.Length);
}
}
6 changes: 6 additions & 0 deletions src/Beutl.Language/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Beutl.Language/Strings.ja.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1222,4 +1222,7 @@ b-editorがダウンロードURLを管理します。</value>
<data name="Smoothing" xml:space="preserve">
<value>なめらかさ</value>
</data>
<data name="ColorShift" xml:space="preserve">
<value>色ずれ</value>
</data>
</root>
3 changes: 3 additions & 0 deletions src/Beutl.Language/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1222,4 +1222,7 @@ and b-editor maintains the download URL.</value>
<data name="Smoothing" xml:space="preserve">
<value>Smoothing</value>
</data>
<data name="ColorShift" xml:space="preserve">
<value>Color Shift</value>
</data>
</root>
11 changes: 11 additions & 0 deletions src/Beutl.Operators/Configure/Effects/EffectsOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,14 @@ public sealed class MosaicOperator : FilterEffectOperator<Mosaic>

public Setter<float> ScaleY { get; set; } = new(Mosaic.ScaleYProperty);
}

public sealed class ColorShiftOperator : FilterEffectOperator<ColorShift>
{
public Setter<PixelPoint> RedOffset { get; set; } = new(ColorShift.RedOffsetProperty);

public Setter<PixelPoint> GreenOffset { get; set; } = new(ColorShift.GreenOffsetProperty);

public Setter<PixelPoint> BlueOffset { get; set; } = new(ColorShift.BlueOffsetProperty);

public Setter<PixelPoint> AlphaOffset { get; set; } = new(ColorShift.AlphaOffsetProperty);
}
5 changes: 5 additions & 0 deletions src/Beutl.Operators/LibraryRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ public static void RegisterAll()
.BindFilterEffect<Mosaic>()
)

.AddMultiple(Strings.ColorShift, m => m
.BindSourceOperator<Configure.Effects.ColorShiftOperator>()
.BindFilterEffect<ColorShift>()
)

.AddGroup("OpenCV", gg => gg
.AddMultiple("CvBlur", m => m
.BindSourceOperator<Configure.Effects.CvBlursOperator>()
Expand Down
7 changes: 7 additions & 0 deletions src/Beutl.ProjectSystem/Operation/SourceOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public sealed class SourceOperation : Hierarchical, IAffectsRender
public static readonly CoreProperty<ICoreList<SourceOperator>> ChildrenProperty;
private readonly HierarchicalList<SourceOperator> _children;
private OperatorEvaluationContext[]? _contexts;
private IRenderer? _lastRenderer;
private int _contextsLength;
private bool _isDirty = true;

Expand Down Expand Up @@ -146,6 +147,12 @@ private void Uninitialize()

private void Initialize(IRenderer renderer, IClock clock)
{
if (_lastRenderer != renderer)
{
_lastRenderer = renderer;
_isDirty = true;
}

if (_isDirty)
{
Uninitialize();
Expand Down

0 comments on commit 6caf498

Please sign in to comment.